From 75feb614e604b2f584872bfbc04ffa65c2b0b0f0 Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:23:34 +0200 Subject: [PATCH] Implement push-based log view Various layout fixes --- .gitignore | 1 + .run/UI debug.run.xml | 2 +- build.gradle.kts | 10 +- .../administration/LogManagement.tsx | 92 +++++++++++++++ .../administration/withConfigPage.tsx | 10 +- .../components/general/CheckboxInput.tsx | 2 +- .../frontend/components/general/Input.tsx | 2 +- .../components/general/SelectInput.tsx | 5 +- .../components/general/withSideMenu.tsx | 2 +- src/main/frontend/index.html | 2 +- src/main/frontend/routes.tsx | 4 +- .../frontend/views/AdministrationView.tsx | 7 +- src/main/frontend/views/MainLayout.tsx | 25 ++--- .../gameyfin/config/ConfigProperties.kt | 28 ++++- .../grimsi/gameyfin/core/SetupDataLoader.kt | 4 + .../gameyfin/core/security/SecurityConfig.kt | 2 +- .../SsoAuthenticationSuccessHandler.kt | 4 +- .../de/grimsi/gameyfin/logs/LogEndpoint.kt | 25 +++++ .../de/grimsi/gameyfin/logs/LogService.kt | 105 ++++++++++++++++++ .../grimsi/gameyfin/logs/dto/LogConfigDto.kt | 9 ++ .../grimsi/gameyfin/system/SystemService.kt | 11 ++ src/main/resources/application.yml | 1 - src/main/resources/log-config-template.xml | 21 ++++ 23 files changed, 341 insertions(+), 33 deletions(-) create mode 100644 src/main/frontend/components/administration/LogManagement.tsx create mode 100644 src/main/kotlin/de/grimsi/gameyfin/logs/LogEndpoint.kt create mode 100644 src/main/kotlin/de/grimsi/gameyfin/logs/LogService.kt create mode 100644 src/main/kotlin/de/grimsi/gameyfin/logs/dto/LogConfigDto.kt create mode 100644 src/main/resources/log-config-template.xml diff --git a/.gitignore b/.gitignore index a874266..d2f1769 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ out/ /.gameyfin/ /src/main/frontend/generated/ /db/ +/logs/ diff --git a/.run/UI debug.run.xml b/.run/UI debug.run.xml index ccfbe55..1a2afd9 100644 --- a/.run/UI debug.run.xml +++ b/.run/UI debug.run.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 63857ed..be69f79 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,11 @@ dependencies { implementation("jakarta.validation:jakarta.validation-api:3.0.2") implementation("org.jetbrains.kotlin:kotlin-reflect") + // Reactive + implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") + // Vaadin Hilla implementation("com.vaadin:vaadin-core") { exclude("com.vaadin:flow-react") @@ -50,10 +55,11 @@ dependencies { // Logging implementation("io.github.oshai:kotlin-logging-jvm:6.0.3") - // Persistence + // Persistence & I/O implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.14") implementation("org.springframework.cloud:spring-cloud-starter") + implementation("commons-io:commons-io:2.16.1") // SSO implementation("org.springframework.boot:spring-boot-starter-oauth2-client") @@ -62,7 +68,7 @@ dependencies { // Notifications implementation("org.springframework.boot:spring-boot-starter-mail") - + // Development developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/src/main/frontend/components/administration/LogManagement.tsx b/src/main/frontend/components/administration/LogManagement.tsx new file mode 100644 index 0000000..c1d4e58 --- /dev/null +++ b/src/main/frontend/components/administration/LogManagement.tsx @@ -0,0 +1,92 @@ +import React, {useEffect, useRef, useState} from "react"; +import {LogEndpoint} from "Frontend/generated/endpoints"; +import withConfigPage from "Frontend/components/administration/withConfigPage"; +import * as Yup from 'yup'; +import ConfigFormField from "Frontend/components/administration/ConfigFormField"; +import {toast} from "sonner"; +import {Button, Code, Divider, Tooltip} from "@nextui-org/react"; +import {ArrowUDownLeft, SortAscending} from "@phosphor-icons/react"; + +function LogManagementLayout({getConfig, formik}: any) { + + const [logEntries, setLogEntries] = useState([]); + const [autoScroll, setAutoScroll] = useState(true); + const [softWrap, setSoftWrap] = useState(false); + const subscribed = useRef(false); + const logEndRef = useRef(null); + + useEffect(() => { + if (subscribed.current) return; + LogEndpoint.getApplicationLogs().onNext((newEntry: string | undefined) => + setLogEntries((currentEntries) => [...currentEntries, newEntry as string]) + ); + subscribed.current = true; + }, []); + + useEffect(() => { + if (formik.isSubmitting == false && formik.submitCount > 0) { + LogEndpoint.reloadLogConfig() + .catch(() => toast.error("Failed to apply log configuration")); + } + }, [formik.isSubmitting]); + + useEffect(() => { + if (autoScroll) { + scrollToBottom(); + } + }, [logEntries, autoScroll, softWrap]); + + function scrollToBottom() { + logEndRef.current?.scrollIntoView(); + } + + return ( +
+
+ + + +
+ +
+
+

Application logs

+
+ + + + + + +
+
+ +
+ + {logEntries.map((entry, index) =>

{entry}

)} +
+ +
+ ); +} + +const validationSchema = Yup.object({ + logs: Yup.object({ + folder: Yup.string().required("Required"), + "max-history-days": Yup.number().required("Required"), + level: Yup.string().required("Required") + }) +}); + +export const LogManagement = withConfigPage(LogManagementLayout, "Logging", "logs", validationSchema); \ No newline at end of file diff --git a/src/main/frontend/components/administration/withConfigPage.tsx b/src/main/frontend/components/administration/withConfigPage.tsx index 7b219b8..a879df4 100644 --- a/src/main/frontend/components/administration/withConfigPage.tsx +++ b/src/main/frontend/components/administration/withConfigPage.tsx @@ -16,11 +16,13 @@ export default function withConfigPage(WrappedComponent: React.ComponentType([]); + const [nestedConfigDtos, setNestedConfigDtos] = useState({}); const [saveMessage, setSaveMessage] = useState(); useEffect(() => { ConfigEndpoint.getAll(configPrefix).then((response: any) => { setConfigDtos(response as ConfigEntryDto[]); + setNestedConfigDtos(toNestedConfig(response as ConfigEntryDto[])); isInitialized.current = true; }); }, []); @@ -34,6 +36,7 @@ export default function withConfigPage(WrappedComponent: React.ComponentType - {(formik: { values: any; isSubmitting: any; }) => ( + {(formik) => (

{title}

@@ -131,7 +135,7 @@ export default function withConfigPage(WrappedComponent: React.ComponentType {formik.isSubmitting ? "" : configSaved ? : "Save"} diff --git a/src/main/frontend/components/general/CheckboxInput.tsx b/src/main/frontend/components/general/CheckboxInput.tsx index 34f1859..0dce16b 100644 --- a/src/main/frontend/components/general/CheckboxInput.tsx +++ b/src/main/frontend/components/general/CheckboxInput.tsx @@ -7,7 +7,7 @@ const CheckboxInput = ({label, ...props}) => { const [field] = useField(props); return ( -
+
{ const [field, meta] = useField(props); return ( -
+
{ const [field] = useField(props); return ( -
+
diff --git a/src/main/frontend/components/general/withSideMenu.tsx b/src/main/frontend/components/general/withSideMenu.tsx index b4f200e..2d9ac9a 100644 --- a/src/main/frontend/components/general/withSideMenu.tsx +++ b/src/main/frontend/components/general/withSideMenu.tsx @@ -51,7 +51,7 @@ export default function withSideMenu(menuItems: MenuItem[]) { ))}
-
+
diff --git a/src/main/frontend/index.html b/src/main/frontend/index.html index b4941f0..cf58aea 100644 --- a/src/main/frontend/index.html +++ b/src/main/frontend/index.html @@ -2,7 +2,7 @@ - + Gameyfin