From e2b8745fdc192f3d49872625652184005a760885 Mon Sep 17 00:00:00 2001 From: sct Date: Fri, 19 Mar 2021 12:59:23 +0000 Subject: [PATCH] feat(logs): add copy to clipboard button to logs page includes various other improvements to the logs page --- package.json | 1 + server/interfaces/api/settingsInterfaces.ts | 1 + server/routes/settings/index.ts | 8 +- .../Settings/SettingsLogs/index.tsx | 182 +++++++++++++++++- src/i18n/locale/en.json | 4 + 5 files changed, 190 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cde38a22..c85d3407 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "bowser": "^2.11.0", "connect-typeorm": "^1.1.4", "cookie-parser": "^1.4.5", + "copy-to-clipboard": "^3.3.1", "country-flag-icons": "^1.2.9", "csurf": "^1.11.0", "email-templates": "^8.0.3", diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 412ed638..72ac9b8a 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -5,6 +5,7 @@ export type LogMessage = { level: string; label: string; message: string; + data?: Record; }; export interface LogsResultsResponse extends PaginatedResponse { diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 993329ed..42039679 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -271,14 +271,18 @@ settingsRoutes.get( const timestamp = line.match(new RegExp(/^.{24}/)) || []; const level = line.match(new RegExp(/\s\[\w+\]/)) || []; const label = line.match(new RegExp(/[^\s]\[\w+\s*\w*\]/)) || []; - const message = line.match(new RegExp(/:\s.*/)) || []; + const message = line.match(new RegExp(/:\s([^{}]+)({.*})?/)) || []; if (level.length && filter.includes(level[0].slice(2, -1))) { logs.push({ timestamp: timestamp[0], level: level.length ? level[0].slice(2, -1) : '', label: label.length ? label[0].slice(2, -1) : '', - message: message.length ? message[0].slice(2) : '', + message: message.length && message[1] ? message[1] : '', + data: + message.length && message[2] + ? JSON.parse(message[2]) + : undefined, }); } }); diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 6b37082e..4dfdd3ab 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import copy from 'copy-to-clipboard'; import useSWR from 'swr'; import { LogMessage, @@ -13,6 +14,9 @@ import PageTitle from '../../Common/PageTitle'; import Table from '../../Common/Table'; import globalMessages from '../../../i18n/globalMessages'; import { useRouter } from 'next/router'; +import Modal from '../../Common/Modal'; +import Transition from '../../Transition'; +import { useToasts } from 'react-toast-notifications'; const messages = defineMessages({ logs: 'Logs', @@ -35,6 +39,10 @@ const messages = defineMessages({ previous: 'Previous', pauseLogs: 'Pause', resumeLogs: 'Resume', + viewDetails: 'View Details', + copyToClipboard: 'Copy to Clipboard', + logDetails: 'Log Details', + extraData: 'Extra Data', }); type Filter = 'debug' | 'info' | 'warn' | 'error'; @@ -42,9 +50,11 @@ type Filter = 'debug' | 'info' | 'warn' | 'error'; const SettingsLogs: React.FC = () => { const router = useRouter(); const intl = useIntl(); + const { addToast } = useToasts(); const [currentFilter, setCurrentFilter] = useState('debug'); const [currentPageSize, setCurrentPageSize] = useState(25); const [refreshInterval, setRefreshInterval] = useState(5000); + const [activeLog, setActiveLog] = useState(null); const page = router.query.page ? Number(router.query.page) : 1; const pageIndex = page - 1; @@ -86,6 +96,18 @@ const SettingsLogs: React.FC = () => { ); }, [currentFilter, currentPageSize]); + const copyLogString = (log: LogMessage): void => { + copy( + `${log.timestamp} [${log.level}]${log.label ? `[${log.label}]` : ''}: ${ + log.message + }${log.data ? `${JSON.stringify(log.data)}` : ''}` + ); + addToast('Copied log message to clipboard.', { + appearance: 'success', + autoDismiss: true, + }); + }; + if (!data && !error) { return ; } @@ -105,6 +127,101 @@ const SettingsLogs: React.FC = () => { intl.formatMessage(globalMessages.settings), ]} /> + + setActiveLog(null)} + cancelText={intl.formatMessage(globalMessages.close)} + onOk={() => (activeLog ? copyLogString(activeLog) : undefined)} + okText={intl.formatMessage(messages.copyToClipboard)} + okButtonType="primary" + > + {activeLog && ( + <> +
+
+ {intl.formatMessage(messages.time)} +
+
+
+ {intl.formatDate(activeLog.timestamp, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + })} +
+
+
+
+
+ {intl.formatMessage(messages.level)} +
+
+
+ + {activeLog.level.toUpperCase()} + +
+
+
+
+
+ {intl.formatMessage(messages.label)} +
+
+
+ {activeLog.label} +
+
+
+
+
+ {intl.formatMessage(messages.message)} +
+
+
+ {activeLog.message} +
+
+
+ {activeLog.data && ( +
+
+ {intl.formatMessage(messages.extraData)} +
+
+ + {JSON.stringify(activeLog.data, null, ' ')} + +
+
+ )} + + )} +
+

{intl.formatMessage(messages.logs)}

@@ -118,13 +235,44 @@ const SettingsLogs: React.FC = () => {

@@ -174,6 +322,7 @@ const SettingsLogs: React.FC = () => { {intl.formatMessage(messages.level)} {intl.formatMessage(messages.label)} {intl.formatMessage(messages.message)} + @@ -207,6 +356,31 @@ const SettingsLogs: React.FC = () => { {row.label} {row.message} + + + + ); })} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index e412e87c..d24ed045 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -451,12 +451,15 @@ "components.Settings.SettingsJobsCache.runnow": "Run Now", "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr Scan", "components.Settings.SettingsJobsCache.unknownJob": "Unknown Job", + "components.Settings.SettingsLogs.copyToClipboard": "Copy to Clipboard", + "components.Settings.SettingsLogs.extraData": "Extra Data", "components.Settings.SettingsLogs.filterDebug": "Debug", "components.Settings.SettingsLogs.filterError": "Error", "components.Settings.SettingsLogs.filterInfo": "Info", "components.Settings.SettingsLogs.filterWarn": "Warning", "components.Settings.SettingsLogs.label": "Label", "components.Settings.SettingsLogs.level": "Severity", + "components.Settings.SettingsLogs.logDetails": "Log Details", "components.Settings.SettingsLogs.logs": "Logs", "components.Settings.SettingsLogs.logsDescription": "You can also view these logs directly via stdout, or in {configDir}/logs/overseerr.log.", "components.Settings.SettingsLogs.message": "Message", @@ -469,6 +472,7 @@ "components.Settings.SettingsLogs.showall": "Show All Logs", "components.Settings.SettingsLogs.showingresults": "Showing {from} to {to} of {total} results", "components.Settings.SettingsLogs.time": "Timestamp", + "components.Settings.SettingsLogs.viewDetails": "View Details", "components.Settings.SettingsUsers.defaultPermissions": "Default User Permissions", "components.Settings.SettingsUsers.localLogin": "Enable Local User Sign-In", "components.Settings.SettingsUsers.save": "Save Changes",