From a6aecac6f149d3b734fd441a78fad55707944b55 Mon Sep 17 00:00:00 2001 From: Danshil Mungur Date: Thu, 11 Feb 2021 22:33:11 +0400 Subject: [PATCH 01/17] Bring branch up to speed Bring branch up to speed --- overseerr-api.yml | 35 +++++++++ server/logger.ts | 1 + server/routes/settings/index.ts | 15 ++++ .../Settings/SettingsLogs/index.tsx | 72 +++++++++++++++++-- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index e436aa628d..9a0837835d 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -2283,6 +2283,41 @@ paths: application/json: schema: $ref: '#/components/schemas/NotificationSettings' + /settings/logs: + get: + summary: Returns logs + description: Returns list of all log items and details + tags: + - settings + parameters: + - in: query + name: rows + schema: + type: number + example: 1000 + default: 1000 + responses: + '200': + description: Server log returned + content: + application/json: + schema: + type: array + items: + type: object + properties: + label: + type: string + example: SERVER + level: + type: string + example: info + message: + type: string + example: Server ready on port 3000 + timestamp: + type: string + example: 2020-12-15T16:20:00.069Z /settings/notifications/email: get: summary: Get email notification settings diff --git a/server/logger.ts b/server/logger.ts index 824de630b0..73b5b43cd8 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -30,6 +30,7 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.splat(), winston.format.timestamp(), + winston.format.json(), hformat ), transports: [ diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index a7dbd3c137..7381f8042f 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -16,6 +16,7 @@ import notificationRoutes from './notifications'; import sonarrRoutes from './sonarr'; import radarrRoutes from './radarr'; import cacheManager, { AvailableCacheIds } from '../../lib/cache'; +import logger from '../../logger'; import { plexFullScanner } from '../../lib/scanners/plex'; const settingsRoutes = Router(); @@ -223,6 +224,20 @@ settingsRoutes.post('/plex/sync', (req, res) => { return res.status(200).json(plexFullScanner.status()); }); +settingsRoutes.get('/logs', (req, res) => { + const options = { + rows: Number(req.query.rows), + fields: null, + }; + + return logger.query(options, (err, results) => { + if (err) { + return res.status(500).json(err); + } + return res.status(200).json(results.file); + }); +}); + settingsRoutes.get('/jobs', (_req, res) => { return res.status(200).json( scheduledJobs.map((job) => ({ diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index bac8cd3bb9..4dd997a4bc 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,14 +1,76 @@ import React from 'react'; +import useSWR from 'swr'; +import Error from '../../../pages/_error'; +import LoadingSpinner from '../../Common/LoadingSpinner'; +import { + FormattedDate, + FormattedTime, + useIntl, + defineMessages, +} from 'react-intl'; -// We will localize this file when the complete version is released. +const messages = defineMessages({ + logs: 'Logs', + logsDescription: + 'You can access your logs directly in stdout (container logs) or looking in /app/config/logs/overseerr.logs', +}); const SettingsLogs: React.FC = () => { + const intl = useIntl(); + const { data, error } = useSWR('/api/v1/settings/logs'); + + if (error) { + return ; + } + + if (!data && !error) { + return ; + } + + if (!data) { + return ; + } + return ( <> -
- This page is still being built. For now, you can access your logs - directly in stdout (container logs) or looking in{' '} - /app/config/logs/overseerr.log. +
+

{intl.formatMessage(messages.logs)}

+

+ {intl.formatMessage(messages.logsDescription)} +

+
+

+ {intl.formatMessage(messages.logs)} +

+

+ {intl.formatMessage(messages.logsDescription)} +

+ +
+ {data?.map((row: any, index: any) => ( +
+ + +   + + + + [{row.level}][{row.label}]: + + {row.message} +
+ ))}
); From d767d47be3424cdd7770f098f93e833c56d9ec31 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Mon, 22 Feb 2021 01:54:15 +0400 Subject: [PATCH 02/17] feat(settings): wip logs ui --- server/logger.ts | 26 ++- server/routes/settings/index.ts | 27 ++- .../Settings/SettingsLogs/index.tsx | 212 ++++++++++++++---- 3 files changed, 212 insertions(+), 53 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 73b5b43cd8..6646c8837f 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -30,8 +30,7 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.splat(), winston.format.timestamp(), - winston.format.json(), - hformat + winston.format.json() ), transports: [ new winston.transports.Console({ @@ -42,17 +41,20 @@ const logger = winston.createLogger({ hformat ), }), - new winston.transports.DailyRotateFile({ - filename: process.env.CONFIG_DIRECTORY - ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` - : path.join(__dirname, '../config/logs/overseerr-%DATE%.log'), - datePattern: 'YYYY-MM-DD', - zippedArchive: true, - maxSize: '20m', - maxFiles: '7d', - createSymlink: true, - symlinkName: 'overseerr.log', + new winston.transports.File({ + filename: path.join(__dirname, '../config/logs/overseerr.log'), }), + // new winston.transports.DailyRotateFile({ + // filename: process.env.CONFIG_DIRECTORY + // ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` + // : path.join(__dirname, '../config/logs/overseerr-%DATE%.log'), + // datePattern: 'YYYY-MM-DD', + // zippedArchive: true, + // maxSize: '20m', + // maxFiles: '7d', + // createSymlink: true, + // symlinkName: 'overseerr.log', + // }), ], }); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 7381f8042f..9175d7e2c9 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -225,12 +225,35 @@ settingsRoutes.post('/plex/sync', (req, res) => { }); settingsRoutes.get('/logs', (req, res) => { + const pageSize = req.query.take ? Number(req.query.take) : 100; + // const filter = req.query.filter ? req.query.filter : 'all'; + const options = { - rows: Number(req.query.rows), + rows: pageSize, fields: null, }; - return logger.query(options, (err, results) => { + // a `rows` query parameter here?? + + // logger.query(options, (err, results) => { + // if (err) { + // return res.status(500).json(err); + // } + // return res + // .status(200) + // .json( + // results.file.filter( + // (row: { + // timestamp: string; + // level: string; + // label: string; + // message: string; + // }) => row.label === filter + // ) + // ); + // }); + + logger.query(options, (err, results) => { if (err) { return res.status(500).json(err); } diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 4dd997a4bc..37972646bf 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import useSWR from 'swr'; import Error from '../../../pages/_error'; import LoadingSpinner from '../../Common/LoadingSpinner'; @@ -8,16 +8,39 @@ import { useIntl, defineMessages, } from 'react-intl'; +import Table from '../../Common/Table'; const messages = defineMessages({ logs: 'Logs', logsDescription: - 'You can access your logs directly in stdout (container logs) or looking in /app/config/logs/overseerr.logs', + "You can access your logs directly in stdout (container logs) or by looking in '/logs/overseerr.log", + time: 'Time', + level: 'Level', + label: 'Label', + message: 'Message', + filterAll: 'All', + filterError: 'Error', + filterInfo: 'Info', + filterDebug: 'Debug', + sortTime: 'Time', + sortLevel: 'Level', + sortLabel: 'Label', }); +type Filter = 'all' | 'error' | 'info' | 'debug'; +type Sort = 'time' | 'level' | 'label'; + const SettingsLogs: React.FC = () => { const intl = useIntl(); - const { data, error } = useSWR('/api/v1/settings/logs'); + // const [pageIndex, setPageIndex] = useState(0); + const [currentFilter, setCurrentFilter] = useState('info'); + const [currentSort, setCurrentSort] = useState('time'); + const { data, error } = useSWR( + `/api/v1/settings/logs?take=100&filter=${currentFilter}&sort=${currentSort}`, + { + refreshInterval: 2, + } + ); if (error) { return ; @@ -33,44 +56,155 @@ const SettingsLogs: React.FC = () => { return ( <> -
+

{intl.formatMessage(messages.logs)}

-

- {intl.formatMessage(messages.logsDescription)} -

-
-

- {intl.formatMessage(messages.logs)} -

-

- {intl.formatMessage(messages.logsDescription)} -

- -
- {data?.map((row: any, index: any) => ( -
- - -   - - - - [{row.level}][{row.label}]: - - {row.message} +
+

+ {intl.formatMessage(messages.logsDescription, { + code: function code(msg) { + return {msg}; + }, + })} +

+
+
+ + + + + + +
+
+ + + + + + +
- ))} +
+ + + + {intl.formatMessage(messages.time)} + {intl.formatMessage(messages.level)} + {intl.formatMessage(messages.label)} + {intl.formatMessage(messages.message)} + + + + {data?.map( + ( + row: { + timestamp: string; + level: string; + label: string; + message: string; + }, + index: number + ) => { + return ( + + +
+ + <> + +
+
+ +
+ {row.level} +
+
+ +
+ {row.label} +
+
+ +
+ {row.message} +
+
+ + ); + } + )} + +
); From 3bb4790065c480f39bbeed46e8a391710070aa0e Mon Sep 17 00:00:00 2001 From: Danshil Mungur Date: Thu, 11 Feb 2021 22:33:11 +0400 Subject: [PATCH 03/17] Bring branch up to speed Bring branch up to speed --- server/logger.ts | 3 +- server/routes/settings/index.ts | 27 ++------------- .../Settings/SettingsLogs/index.tsx | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 6646c8837f..eb08ecbeb6 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -30,7 +30,8 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.splat(), winston.format.timestamp(), - winston.format.json() + hformat + winston.format.json(), ), transports: [ new winston.transports.Console({ diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 9175d7e2c9..7381f8042f 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -225,35 +225,12 @@ settingsRoutes.post('/plex/sync', (req, res) => { }); settingsRoutes.get('/logs', (req, res) => { - const pageSize = req.query.take ? Number(req.query.take) : 100; - // const filter = req.query.filter ? req.query.filter : 'all'; - const options = { - rows: pageSize, + rows: Number(req.query.rows), fields: null, }; - // a `rows` query parameter here?? - - // logger.query(options, (err, results) => { - // if (err) { - // return res.status(500).json(err); - // } - // return res - // .status(200) - // .json( - // results.file.filter( - // (row: { - // timestamp: string; - // level: string; - // label: string; - // message: string; - // }) => row.label === filter - // ) - // ); - // }); - - logger.query(options, (err, results) => { + return logger.query(options, (err, results) => { if (err) { return res.status(500).json(err); } diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 37972646bf..2c7172f95a 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -206,6 +206,39 @@ const SettingsLogs: React.FC = () => {
+

+ {intl.formatMessage(messages.logs)} +

+

+ {intl.formatMessage(messages.logsDescription)} +

+ +
+ {data?.map((row: any, index: any) => ( +
+ + +   + + + + [{row.level}][{row.label}]: + + {row.message} +
+ ))} +
); }; From 9b96bc04531b415bcf06e7fb0fb09d22ae73e374 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Mon, 22 Feb 2021 01:54:15 +0400 Subject: [PATCH 04/17] feat(settings): wip logs ui --- server/logger.ts | 3 +- server/routes/settings/index.ts | 27 +++++++++++++-- .../Settings/SettingsLogs/index.tsx | 33 ------------------- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index eb08ecbeb6..6646c8837f 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -30,8 +30,7 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.splat(), winston.format.timestamp(), - hformat - winston.format.json(), + winston.format.json() ), transports: [ new winston.transports.Console({ diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 7381f8042f..9175d7e2c9 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -225,12 +225,35 @@ settingsRoutes.post('/plex/sync', (req, res) => { }); settingsRoutes.get('/logs', (req, res) => { + const pageSize = req.query.take ? Number(req.query.take) : 100; + // const filter = req.query.filter ? req.query.filter : 'all'; + const options = { - rows: Number(req.query.rows), + rows: pageSize, fields: null, }; - return logger.query(options, (err, results) => { + // a `rows` query parameter here?? + + // logger.query(options, (err, results) => { + // if (err) { + // return res.status(500).json(err); + // } + // return res + // .status(200) + // .json( + // results.file.filter( + // (row: { + // timestamp: string; + // level: string; + // label: string; + // message: string; + // }) => row.label === filter + // ) + // ); + // }); + + logger.query(options, (err, results) => { if (err) { return res.status(500).json(err); } diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 2c7172f95a..37972646bf 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -206,39 +206,6 @@ const SettingsLogs: React.FC = () => {
-

- {intl.formatMessage(messages.logs)} -

-

- {intl.formatMessage(messages.logsDescription)} -

- -
- {data?.map((row: any, index: any) => ( -
- - -   - - - - [{row.level}][{row.label}]: - - {row.message} -
- ))} -
); }; From 9cd3b9a0d3e12124e3450e3e4026f51e5adf80cf Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Mon, 22 Feb 2021 01:56:17 +0000 Subject: [PATCH 05/17] Various log page fixes & improvements --- overseerr-api.yml | 19 +- server/interfaces/api/settingsInterfaces.ts | 13 ++ server/routes/settings/index.ts | 75 ++++--- .../Settings/SettingsLogs/index.tsx | 203 ++++++++++++------ 4 files changed, 214 insertions(+), 96 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index 9a0837835d..c175e18342 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -2291,11 +2291,24 @@ paths: - settings parameters: - in: query - name: rows + name: take schema: type: number - example: 1000 - default: 1000 + nullable: true + example: 25 + - in: query + name: skip + schema: + type: number + nullable: true + example: 0 + - in: query + name: filter + schema: + type: string + nullable: true + enum: [debug, info, warn, error] + default: debug responses: '200': description: Server log returned diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index b687d5977b..abfcde24a7 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -1,3 +1,16 @@ +import type { PaginatedResponse } from './common'; + +export type LogMessage = { + time: string; + level: string; + label: string; + message: string; +}; + +export interface LogsResultsResponse extends PaginatedResponse { + results: LogMessage[]; +} + export interface SettingsAboutResponse { version: string; totalRequests: number; diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 9175d7e2c9..e4bfb276e2 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -11,7 +11,10 @@ import { merge, omit } from 'lodash'; import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; import { getAppVersion } from '../../utils/appVersion'; -import { SettingsAboutResponse } from '../../interfaces/api/settingsInterfaces'; +import { + LogsResultsResponse, + SettingsAboutResponse, +} from '../../interfaces/api/settingsInterfaces'; import notificationRoutes from './notifications'; import sonarrRoutes from './sonarr'; import radarrRoutes from './radarr'; @@ -224,40 +227,60 @@ settingsRoutes.post('/plex/sync', (req, res) => { return res.status(200).json(plexFullScanner.status()); }); -settingsRoutes.get('/logs', (req, res) => { - const pageSize = req.query.take ? Number(req.query.take) : 100; - // const filter = req.query.filter ? req.query.filter : 'all'; +settingsRoutes.get('/logs', (req, res, next) => { + const pageSize = req.query.take ? Number(req.query.take) : 25; + const skip = req.query.skip ? Number(req.query.skip) : 0; + + let filter: string[] = []; + switch (req.query.filter) { + case 'debug': + filter.push('debug'); + // falls through + case 'info': + filter.push('info'); + // falls through + case 'warn': + filter.push('warn'); + // falls through + case 'error': + filter.push('error'); + break; + default: + filter = ['debug', 'info', 'warn', 'error']; + } const options = { rows: pageSize, fields: null, }; - // a `rows` query parameter here?? - - // logger.query(options, (err, results) => { - // if (err) { - // return res.status(500).json(err); - // } - // return res - // .status(200) - // .json( - // results.file.filter( - // (row: { - // timestamp: string; - // level: string; - // label: string; - // message: string; - // }) => row.label === filter - // ) - // ); - // }); - logger.query(options, (err, results) => { if (err) { - return res.status(500).json(err); + logger.error(err.message); + return next({ + status: 500, + message: 'Something went wrong while fetching logs.', + }); } - return res.status(200).json(results.file); + + const filteredLogs = results.file.filter( + (row: { + timestamp: string; + level: string; + label: string; + message: string; + }) => filter.includes(row.level) + ); + + return res.status(200).json({ + pageInfo: { + pages: 1, + pageSize, + results: filteredLogs.length, + page: Math.ceil(skip / pageSize) + 1, + }, + results: filteredLogs, + } as LogsResultsResponse); }); }); diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 37972646bf..8b90a839a4 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import useSWR from 'swr'; -import Error from '../../../pages/_error'; import LoadingSpinner from '../../Common/LoadingSpinner'; import { FormattedDate, @@ -9,43 +8,47 @@ import { defineMessages, } from 'react-intl'; import Table from '../../Common/Table'; +import Button from '../../Common/Button'; +import Badge from '../../Common/Badge'; const messages = defineMessages({ logs: 'Logs', logsDescription: - "You can access your logs directly in stdout (container logs) or by looking in '/logs/overseerr.log", - time: 'Time', - level: 'Level', + 'You can also view these logs directly via stdout, or in {configDir}/logs/overseerr.log', + time: 'Timestamp', + level: 'Severity', label: 'Label', message: 'Message', - filterAll: 'All', - filterError: 'Error', - filterInfo: 'Info', filterDebug: 'Debug', - sortTime: 'Time', - sortLevel: 'Level', - sortLabel: 'Label', + filterInfo: 'Info', + filterWarn: 'Warning', + filterError: 'Error', + noresults: 'No results.', + showall: 'Show All Logs', + showingresults: + 'Showing {from} to {to} of {total} results', + resultsperpage: 'Display {pageSize} results per page', + next: 'Next', + previous: 'Previous', }); -type Filter = 'all' | 'error' | 'info' | 'debug'; -type Sort = 'time' | 'level' | 'label'; +type Filter = 'debug' | 'info' | 'warn' | 'error'; const SettingsLogs: React.FC = () => { const intl = useIntl(); - // const [pageIndex, setPageIndex] = useState(0); - const [currentFilter, setCurrentFilter] = useState('info'); - const [currentSort, setCurrentSort] = useState('time'); + const [pageIndex, setPageIndex] = useState(0); + const [currentFilter, setCurrentFilter] = useState('debug'); + const [currentPageSize, setCurrentPageSize] = useState(25); + const { data, error } = useSWR( - `/api/v1/settings/logs?take=100&filter=${currentFilter}&sort=${currentSort}`, + `/api/v1/settings/logs?take=${currentPageSize}&skip=${ + pageIndex * currentPageSize + }&filter=${currentFilter}`, { refreshInterval: 2, } ); - if (error) { - return ; - } - if (!data && !error) { return ; } @@ -54,20 +57,24 @@ const SettingsLogs: React.FC = () => { return ; } + const hasNextPage = data.pageInfo.pages > pageIndex + 1; + const hasPrevPage = pageIndex > 0; + return ( <>

{intl.formatMessage(messages.logs)}

-
+

{intl.formatMessage(messages.logsDescription, { code: function code(msg) { return {msg}; }, + configDir: '/config', })}

-
-
+
+
{ value={currentFilter} className="rounded-r-only" > - - - - -
-
- - - - - -
@@ -154,7 +125,7 @@ const SettingsLogs: React.FC = () => { - {data?.map( + {data?.results.map( ( row: { timestamp: string; @@ -186,7 +157,18 @@ const SettingsLogs: React.FC = () => {
- {row.level} + {row.level === 'debug' && ( + {row.level} + )} + {row.level === 'info' && ( + {row.level} + )} + {row.level === 'warn' && ( + {row.level} + )} + {row.level === 'error' && ( + {row.level} + )}
@@ -203,6 +185,93 @@ const SettingsLogs: React.FC = () => { ); } )} + + {data.results.length === 0 && ( + + +
+ + {intl.formatMessage(messages.noresults)} + + {currentFilter !== 'debug' && ( +
+ +
+ )} +
+
+ + )} + + + + +
From 59e2f12a904763dd261c64723fd2babc02bc4c40 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Mon, 22 Feb 2021 19:50:37 +0400 Subject: [PATCH 06/17] feat(settings): finished logs ui --- server/routes/settings/index.ts | 9 +++-- .../Settings/SettingsLogs/index.tsx | 33 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index e4bfb276e2..63cdf9369d 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -250,7 +250,8 @@ settingsRoutes.get('/logs', (req, res, next) => { } const options = { - rows: pageSize, + // only get 1000 most recent rows + rows: 1000, fields: null, }; @@ -272,14 +273,16 @@ settingsRoutes.get('/logs', (req, res, next) => { }) => filter.includes(row.level) ); + const displayedLogs = filteredLogs.slice(skip, skip + pageSize); + return res.status(200).json({ pageInfo: { - pages: 1, + pages: Math.ceil(filteredLogs.length / pageSize), pageSize, results: filteredLogs.length, page: Math.ceil(skip / pageSize) + 1, }, - results: filteredLogs, + results: displayedLogs, } as LogsResultsResponse); }); }); diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 8b90a839a4..1373c27d80 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -10,6 +10,7 @@ import { import Table from '../../Common/Table'; import Button from '../../Common/Button'; import Badge from '../../Common/Badge'; +import { LogsResultsResponse } from '../../../../server/interfaces/api/settingsInterfaces'; const messages = defineMessages({ logs: 'Logs', @@ -40,15 +41,17 @@ const SettingsLogs: React.FC = () => { const [currentFilter, setCurrentFilter] = useState('debug'); const [currentPageSize, setCurrentPageSize] = useState(25); - const { data, error } = useSWR( + const { data, error } = useSWR( `/api/v1/settings/logs?take=${currentPageSize}&skip=${ pageIndex * currentPageSize }&filter=${currentFilter}`, { - refreshInterval: 2, + refreshInterval: 5000, } ); + const { data: appDataResponse } = useSWR('/api/v1/status/appdata'); + if (!data && !error) { return ; } @@ -70,7 +73,7 @@ const SettingsLogs: React.FC = () => { code: function code(msg) { return {msg}; }, - configDir: '/config', + configDir: appDataResponse.appDataPath, })}

@@ -93,7 +96,7 @@ const SettingsLogs: React.FC = () => { id="filter" name="filter" onChange={(e) => { - // setPageIndex(0); + setPageIndex(0); setCurrentFilter(e.target.value as Filter); }} value={currentFilter} @@ -128,7 +131,7 @@ const SettingsLogs: React.FC = () => { {data?.results.map( ( row: { - timestamp: string; + time: string; level: string; label: string; message: string; @@ -140,14 +143,14 @@ const SettingsLogs: React.FC = () => {
<> {
{row.level === 'debug' && ( - {row.level} + + {row.level.toUpperCase()} + )} {row.level === 'info' && ( - {row.level} + + {row.level.toUpperCase()} + )} {row.level === 'warn' && ( - {row.level} + + {row.level.toUpperCase()} + )} {row.level === 'error' && ( - {row.level} + + {row.level.toUpperCase()} + )}
From 8cbc31eeda5b83e3b7fccfb7718879d0a933cb02 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Mon, 22 Feb 2021 20:57:43 +0400 Subject: [PATCH 07/17] style(settings): consistent parameter names + i18n extract --- server/interfaces/api/settingsInterfaces.ts | 2 +- src/components/Settings/SettingsLogs/index.tsx | 6 +++--- src/i18n/locale/en.json | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index abfcde24a7..67925bb322 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -1,7 +1,7 @@ import type { PaginatedResponse } from './common'; export type LogMessage = { - time: string; + timestamp: string; level: string; label: string; message: string; diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 1373c27d80..6572480045 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -131,7 +131,7 @@ const SettingsLogs: React.FC = () => { {data?.results.map( ( row: { - time: string; + timestamp: string; level: string; label: string; message: string; @@ -143,14 +143,14 @@ const SettingsLogs: React.FC = () => {
<> stdout, or in {configDir}/logs/overseerr.log", + "components.Settings.SettingsLogs.message": "Message", + "components.Settings.SettingsLogs.next": "Next", + "components.Settings.SettingsLogs.noresults": "No results.", + "components.Settings.SettingsLogs.previous": "Previous", + "components.Settings.SettingsLogs.resultsperpage": "Display {pageSize} results per page", + "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.SettingsJobsCache.sonarr-scan": "Sonarr Scan", "components.Settings.SettingsJobsCache.unknownJob": "Unknown Job", "components.Settings.SettingsUsers.defaultPermissions": "Default User Permissions", From 881339c98139443f3b1335ab850b49c14ddacc8b Mon Sep 17 00:00:00 2001 From: Danshil M Date: Tue, 23 Feb 2021 02:12:38 +0400 Subject: [PATCH 08/17] feat(settings): use tmp.log for json format and keep logrotate --- server/logger.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 6646c8837f..29442fc60a 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -30,7 +30,7 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.splat(), winston.format.timestamp(), - winston.format.json() + hformat ), transports: [ new winston.transports.Console({ @@ -42,19 +42,24 @@ const logger = winston.createLogger({ ), }), new winston.transports.File({ - filename: path.join(__dirname, '../config/logs/overseerr.log'), + filename: path.join(__dirname, '../config/logs/tmp.log'), + format: winston.format.combine( + winston.format.splat(), + winston.format.timestamp(), + winston.format.json() + ), + }), + new winston.transports.DailyRotateFile({ + filename: process.env.CONFIG_DIRECTORY + ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` + : path.join(__dirname, '../config/logs/overseerr-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '7d', + createSymlink: true, + symlinkName: 'overseerr.log', }), - // new winston.transports.DailyRotateFile({ - // filename: process.env.CONFIG_DIRECTORY - // ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` - // : path.join(__dirname, '../config/logs/overseerr-%DATE%.log'), - // datePattern: 'YYYY-MM-DD', - // zippedArchive: true, - // maxSize: '20m', - // maxFiles: '7d', - // createSymlink: true, - // symlinkName: 'overseerr.log', - // }), ], }); From 945a79af275738498dc5d4a02577814e69f59163 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 01:03:30 +0400 Subject: [PATCH 09/17] feat(settings): revert use of json formatted logs + add pause logs button revert back to using the hformat custom format only for winstonjs --- server/logger.ts | 8 --- server/routes/settings/index.ts | 72 ++++++++++++------- .../Settings/SettingsLogs/index.tsx | 44 ++++++++---- 3 files changed, 76 insertions(+), 48 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 29442fc60a..824de630b0 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -41,14 +41,6 @@ const logger = winston.createLogger({ hformat ), }), - new winston.transports.File({ - filename: path.join(__dirname, '../config/logs/tmp.log'), - format: winston.format.combine( - winston.format.splat(), - winston.format.timestamp(), - winston.format.json() - ), - }), new winston.transports.DailyRotateFile({ filename: process.env.CONFIG_DIRECTORY ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 63cdf9369d..6e8b55092d 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -1,7 +1,8 @@ import { Router } from 'express'; -import { getSettings, Library, MainSettings } from '../../lib/settings'; +import fs from 'fs'; +import { merge, omit } from 'lodash'; +import path from 'path'; import { getRepository } from 'typeorm'; -import { User } from '../../entity/User'; import PlexAPI from '../../api/plexapi'; import PlexTvAPI from '../../api/plextv'; import { scheduledJobs } from '../../job/schedule'; @@ -10,12 +11,22 @@ import { isAuthenticated } from '../../middleware/auth'; import { merge, omit } from 'lodash'; import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; -import { getAppVersion } from '../../utils/appVersion'; +import { User } from '../../entity/User'; import { + LogMessage, LogsResultsResponse, SettingsAboutResponse, } from '../../interfaces/api/settingsInterfaces'; +import { jobPlexFullSync } from '../../job/plexsync'; +import { scheduledJobs } from '../../job/schedule'; +import cacheManager, { AvailableCacheIds } from '../../lib/cache'; +import { Permission } from '../../lib/permissions'; +import { getSettings, Library, MainSettings } from '../../lib/settings'; +import logger from '../../logger'; +import { isAuthenticated } from '../../middleware/auth'; +import { getAppVersion } from '../../utils/appVersion'; import notificationRoutes from './notifications'; +import radarrRoutes from './radarr'; import sonarrRoutes from './sonarr'; import radarrRoutes from './radarr'; import cacheManager, { AvailableCacheIds } from '../../lib/cache'; @@ -249,29 +260,34 @@ settingsRoutes.get('/logs', (req, res, next) => { filter = ['debug', 'info', 'warn', 'error']; } - const options = { - // only get 1000 most recent rows - rows: 1000, - fields: null, - }; - - logger.query(options, (err, results) => { - if (err) { - logger.error(err.message); - return next({ - status: 500, - message: 'Something went wrong while fetching logs.', + const logFile = process.env.CONFIG_DIRECTORY + ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log` + : path.join(__dirname, '../../../config/logs/overseerr.log'); + const logs: LogMessage[] = []; + + try { + fs.readFileSync(logFile) + .toString() + .split('\n') + .forEach((line) => { + if (!line.length) return; + + 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.*/)) || []; + + 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, -1) : '', + }); }); - } - const filteredLogs = results.file.filter( - (row: { - timestamp: string; - level: string; - label: string; - message: string; - }) => filter.includes(row.level) - ); + const filteredLogs = logs + .filter((row) => filter.includes(row.level)) + .reverse(); const displayedLogs = filteredLogs.slice(skip, skip + pageSize); @@ -284,7 +300,13 @@ settingsRoutes.get('/logs', (req, res, next) => { }, results: displayedLogs, } as LogsResultsResponse); - }); + } catch (error) { + logger.error(error.message, { label: 'Settings Router' }); + return next({ + status: 500, + message: 'Something went wrong while fetching the logs', + }); + } }); settingsRoutes.get('/jobs', (_req, res) => { diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 6572480045..5b1c826e6b 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,16 +1,16 @@ import React, { useState } from 'react'; -import useSWR from 'swr'; -import LoadingSpinner from '../../Common/LoadingSpinner'; import { + defineMessages, FormattedDate, FormattedTime, useIntl, - defineMessages, } from 'react-intl'; -import Table from '../../Common/Table'; -import Button from '../../Common/Button'; -import Badge from '../../Common/Badge'; +import useSWR from 'swr'; import { LogsResultsResponse } from '../../../../server/interfaces/api/settingsInterfaces'; +import Badge from '../../Common/Badge'; +import Button from '../../Common/Button'; +import LoadingSpinner from '../../Common/LoadingSpinner'; +import Table from '../../Common/Table'; const messages = defineMessages({ logs: 'Logs', @@ -31,6 +31,8 @@ const messages = defineMessages({ resultsperpage: 'Display {pageSize} results per page', next: 'Next', previous: 'Previous', + pauseLogs: 'Pause Logs', + resumeLogs: 'Resume Logs', }); type Filter = 'debug' | 'info' | 'warn' | 'error'; @@ -39,24 +41,26 @@ const SettingsLogs: React.FC = () => { const intl = useIntl(); const [pageIndex, setPageIndex] = useState(0); const [currentFilter, setCurrentFilter] = useState('debug'); - const [currentPageSize, setCurrentPageSize] = useState(25); + const [currentPageSize, setCurrentPageSize] = useState(25); + const [refreshInterval, setRefreshInterval] = useState(5000); + + const toggleLogs = () => { + setRefreshInterval(refreshInterval === 5000 ? 0 : 5000); + }; const { data, error } = useSWR( `/api/v1/settings/logs?take=${currentPageSize}&skip=${ pageIndex * currentPageSize }&filter=${currentFilter}`, { - refreshInterval: 5000, + refreshInterval: refreshInterval, + revalidateOnFocus: false, } ); - const { data: appDataResponse } = useSWR('/api/v1/status/appdata'); + const { data: appData } = useSWR('/api/v1/status/appdata'); - if (!data && !error) { - return ; - } - - if (!data) { + if (!data || (!data && !error)) { return ; } @@ -73,11 +77,20 @@ const SettingsLogs: React.FC = () => { code: function code(msg) { return {msg}; }, - configDir: appDataResponse.appDataPath, + configDir: appData.appDataPath, })}

+ {
+ {/* {data && data} */} From 840951eacdf78607c9531bdcb512e233fd2d3a7e Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 01:18:22 +0400 Subject: [PATCH 10/17] feat(settings): ran i18n:extract --- server/routes/settings/index.ts | 10 +--------- src/i18n/locale/en.json | 6 ++++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 6e8b55092d..106c01ad44 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -5,10 +5,6 @@ import path from 'path'; import { getRepository } from 'typeorm'; import PlexAPI from '../../api/plexapi'; import PlexTvAPI from '../../api/plextv'; -import { scheduledJobs } from '../../job/schedule'; -import { Permission } from '../../lib/permissions'; -import { isAuthenticated } from '../../middleware/auth'; -import { merge, omit } from 'lodash'; import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; import { User } from '../../entity/User'; @@ -17,10 +13,10 @@ import { LogsResultsResponse, SettingsAboutResponse, } from '../../interfaces/api/settingsInterfaces'; -import { jobPlexFullSync } from '../../job/plexsync'; import { scheduledJobs } from '../../job/schedule'; import cacheManager, { AvailableCacheIds } from '../../lib/cache'; import { Permission } from '../../lib/permissions'; +import { plexFullScanner } from '../../lib/scanners/plex'; import { getSettings, Library, MainSettings } from '../../lib/settings'; import logger from '../../logger'; import { isAuthenticated } from '../../middleware/auth'; @@ -28,10 +24,6 @@ import { getAppVersion } from '../../utils/appVersion'; import notificationRoutes from './notifications'; import radarrRoutes from './radarr'; import sonarrRoutes from './sonarr'; -import radarrRoutes from './radarr'; -import cacheManager, { AvailableCacheIds } from '../../lib/cache'; -import logger from '../../logger'; -import { plexFullScanner } from '../../lib/scanners/plex'; const settingsRoutes = Router(); diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 1210aae7d6..1227f9b021 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -451,6 +451,8 @@ "components.Settings.SettingsJobsCache.process": "Process", "components.Settings.SettingsJobsCache.radarr-scan": "Radarr Scan", "components.Settings.SettingsJobsCache.runnow": "Run Now", + "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr Scan", + "components.Settings.SettingsJobsCache.unknownJob": "Unknown Job", "components.Settings.SettingsLogs.filterDebug": "Debug", "components.Settings.SettingsLogs.filterError": "Error", "components.Settings.SettingsLogs.filterInfo": "Info", @@ -462,13 +464,13 @@ "components.Settings.SettingsLogs.message": "Message", "components.Settings.SettingsLogs.next": "Next", "components.Settings.SettingsLogs.noresults": "No results.", + "components.Settings.SettingsLogs.pauseLogs": "Pause Logs", "components.Settings.SettingsLogs.previous": "Previous", "components.Settings.SettingsLogs.resultsperpage": "Display {pageSize} results per page", + "components.Settings.SettingsLogs.resumeLogs": "Resume Logs", "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.SettingsJobsCache.sonarr-scan": "Sonarr Scan", - "components.Settings.SettingsJobsCache.unknownJob": "Unknown Job", "components.Settings.SettingsUsers.defaultPermissions": "Default User Permissions", "components.Settings.SettingsUsers.localLogin": "Enable Local User Sign-In", "components.Settings.SettingsUsers.save": "Save Changes", From f4649b19f97cfa408df9bad24c81ba93d32c0f70 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 01:20:18 +0400 Subject: [PATCH 11/17] feat(settings): cleanup --- src/components/Settings/SettingsLogs/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 5b1c826e6b..16f75018a0 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -131,7 +131,6 @@ const SettingsLogs: React.FC = () => { - {/* {data && data} */}
From 3dc628fb376d1b68bc0cd8c8141420e82d0d34b7 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 01:32:12 +0400 Subject: [PATCH 12/17] feat(settings): revert unnecessary change --- src/components/Settings/SettingsLogs/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 16f75018a0..91b0dd7660 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -60,7 +60,11 @@ const SettingsLogs: React.FC = () => { const { data: appData } = useSWR('/api/v1/status/appdata'); - if (!data || (!data && !error)) { + if (!data && !error) { + return ; + } + + if (!data) { return ; } From e563c7bd60530d0c72a7d2a453a41689d76a477c Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 02:15:39 +0400 Subject: [PATCH 13/17] feat(settings): set rate limit for logs page this is currently a limit of 50 requests per minute --- package.json | 2 + server/routes/settings/index.ts | 137 +++++++++++++++++--------------- yarn.lock | 12 +++ 3 files changed, 85 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index 0adf6116b4..b840aa0bf3 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "email-templates": "^8.0.3", "express": "^4.17.1", "express-openapi-validator": "^4.12.5", + "express-rate-limit": "^5.2.6", "express-session": "^1.17.1", "formik": "^2.2.6", "gravatar-url": "^3.1.0", @@ -89,6 +90,7 @@ "@types/csurf": "^1.11.0", "@types/email-templates": "^8.0.2", "@types/express": "^4.17.11", + "@types/express-rate-limit": "^5.1.1", "@types/express-session": "^1.17.3", "@types/lodash": "^4.14.168", "@types/node": "^14.14.35", diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 106c01ad44..03019501be 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -24,6 +24,7 @@ import { getAppVersion } from '../../utils/appVersion'; import notificationRoutes from './notifications'; import radarrRoutes from './radarr'; import sonarrRoutes from './sonarr'; +import rateLimit from 'express-rate-limit'; const settingsRoutes = Router(); @@ -230,76 +231,80 @@ settingsRoutes.post('/plex/sync', (req, res) => { return res.status(200).json(plexFullScanner.status()); }); -settingsRoutes.get('/logs', (req, res, next) => { - const pageSize = req.query.take ? Number(req.query.take) : 25; - const skip = req.query.skip ? Number(req.query.skip) : 0; - - let filter: string[] = []; - switch (req.query.filter) { - case 'debug': - filter.push('debug'); - // falls through - case 'info': - filter.push('info'); - // falls through - case 'warn': - filter.push('warn'); - // falls through - case 'error': - filter.push('error'); - break; - default: - filter = ['debug', 'info', 'warn', 'error']; - } - - const logFile = process.env.CONFIG_DIRECTORY - ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log` - : path.join(__dirname, '../../../config/logs/overseerr.log'); - const logs: LogMessage[] = []; +settingsRoutes.get( + '/logs', + rateLimit({ windowMs: 60 * 1000, max: 50 }), + (req, res, next) => { + const pageSize = req.query.take ? Number(req.query.take) : 25; + const skip = req.query.skip ? Number(req.query.skip) : 0; + + let filter: string[] = []; + switch (req.query.filter) { + case 'debug': + filter.push('debug'); + // falls through + case 'info': + filter.push('info'); + // falls through + case 'warn': + filter.push('warn'); + // falls through + case 'error': + filter.push('error'); + break; + default: + filter = ['debug', 'info', 'warn', 'error']; + } - try { - fs.readFileSync(logFile) - .toString() - .split('\n') - .forEach((line) => { - if (!line.length) return; - - 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.*/)) || []; - - 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, -1) : '', + const logFile = process.env.CONFIG_DIRECTORY + ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log` + : path.join(__dirname, '../../../config/logs/overseerr.log'); + const logs: LogMessage[] = []; + + try { + fs.readFileSync(logFile) + .toString() + .split('\n') + .forEach((line) => { + if (!line.length) return; + + 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.*/)) || []; + + 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, -1) : '', + }); }); - }); - - const filteredLogs = logs - .filter((row) => filter.includes(row.level)) - .reverse(); - - const displayedLogs = filteredLogs.slice(skip, skip + pageSize); - return res.status(200).json({ - pageInfo: { - pages: Math.ceil(filteredLogs.length / pageSize), - pageSize, - results: filteredLogs.length, - page: Math.ceil(skip / pageSize) + 1, - }, - results: displayedLogs, - } as LogsResultsResponse); - } catch (error) { - logger.error(error.message, { label: 'Settings Router' }); - return next({ - status: 500, - message: 'Something went wrong while fetching the logs', - }); + const filteredLogs = logs + .filter((row) => filter.includes(row.level)) + .reverse(); + + const displayedLogs = filteredLogs.slice(skip, skip + pageSize); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(filteredLogs.length / pageSize), + pageSize, + results: filteredLogs.length, + page: Math.ceil(skip / pageSize) + 1, + }, + results: displayedLogs, + } as LogsResultsResponse); + } catch (error) { + logger.error(error.message, { label: 'Settings Router' }); + return next({ + status: 500, + message: 'Something went wrong while fetching the logs', + }); + } } -}); +); settingsRoutes.get('/jobs', (_req, res) => { return res.status(200).json( diff --git a/yarn.lock b/yarn.lock index 8e40f1987a..4a08e67185 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2097,6 +2097,13 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@types/express-rate-limit@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-rate-limit/-/express-rate-limit-5.1.1.tgz#e5b0239d18c1580e52ae56dce4248333302a1dc8" + integrity sha512-6oMYZBLlhxC5sdcRXXz528QyfGz3zTy9YdHwqlxLfgx5Cd3zwYaUjjPpJcaTtHmRefLi9P8kLBPz2wB7yz4JtQ== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core@*": version "4.17.9" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" @@ -5714,6 +5721,11 @@ express-openapi-validator@^4.12.5: ono "^7.1.3" path-to-regexp "^6.2.0" +express-rate-limit@^5.2.6: + version "5.2.6" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0" + integrity sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA== + express-session@^1.15.6, express-session@^1.17.1: version "1.17.1" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" From 43ce5ecea89f94c6538fdef895b12f887e4bc302 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 02:17:51 +0400 Subject: [PATCH 14/17] refactor(settings): organise imports --- server/routes/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 03019501be..90b437d6c0 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -1,4 +1,5 @@ import { Router } from 'express'; +import rateLimit from 'express-rate-limit'; import fs from 'fs'; import { merge, omit } from 'lodash'; import path from 'path'; @@ -24,7 +25,6 @@ import { getAppVersion } from '../../utils/appVersion'; import notificationRoutes from './notifications'; import radarrRoutes from './radarr'; import sonarrRoutes from './sonarr'; -import rateLimit from 'express-rate-limit'; const settingsRoutes = Router(); From bbafdfb7ca614a9ff49754ca1606e4fb1bf3f252 Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 04:42:14 +0400 Subject: [PATCH 15/17] feat(settings): remember logs display also improved formatting + cleanup --- src/components/RequestList/index.tsx | 2 +- .../Settings/SettingsLogs/index.tsx | 264 +++++++++--------- src/i18n/locale/en.json | 4 +- 3 files changed, 131 insertions(+), 139 deletions(-) diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 7d1aceff4f..bba70847d4 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -59,7 +59,7 @@ const RequestList: React.FC = () => { } }, []); - // Set fitler values to local storage any time they are changed + // Set filter values to local storage any time they are changed useEffect(() => { window.localStorage.setItem( 'rl-filter-settings', diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 91b0dd7660..183ffa134f 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -1,12 +1,10 @@ -import React, { useState } from 'react'; -import { - defineMessages, - FormattedDate, - FormattedTime, - useIntl, -} from 'react-intl'; +import React, { useEffect, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; -import { LogsResultsResponse } from '../../../../server/interfaces/api/settingsInterfaces'; +import { + LogMessage, + LogsResultsResponse, +} from '../../../../server/interfaces/api/settingsInterfaces'; import Badge from '../../Common/Badge'; import Button from '../../Common/Button'; import LoadingSpinner from '../../Common/LoadingSpinner'; @@ -15,7 +13,7 @@ import Table from '../../Common/Table'; const messages = defineMessages({ logs: 'Logs', logsDescription: - 'You can also view these logs directly via stdout, or in {configDir}/logs/overseerr.log', + 'You can also view these logs directly via stdout, or in {configDir}/logs/overseerr.log.', time: 'Timestamp', level: 'Severity', label: 'Label', @@ -31,8 +29,8 @@ const messages = defineMessages({ resultsperpage: 'Display {pageSize} results per page', next: 'Next', previous: 'Previous', - pauseLogs: 'Pause Logs', - resumeLogs: 'Resume Logs', + pauseLogs: 'Pause', + resumeLogs: 'Resume', }); type Filter = 'debug' | 'info' | 'warn' | 'error'; @@ -60,6 +58,27 @@ const SettingsLogs: React.FC = () => { const { data: appData } = useSWR('/api/v1/status/appdata'); + useEffect(() => { + const displayString = window.localStorage.getItem('logs-display-settings'); + + if (displayString) { + const displaySettings = JSON.parse(displayString); + + setCurrentFilter(displaySettings.currentFilter); + setCurrentPageSize(displaySettings.currentPageSize); + } + }, []); + + useEffect(() => { + window.localStorage.setItem( + 'logs-display-settings', + JSON.stringify({ + currentFilter, + currentPageSize, + }) + ); + }, [currentFilter, currentPageSize]); + if (!data && !error) { return ; } @@ -75,64 +94,64 @@ const SettingsLogs: React.FC = () => { <>

{intl.formatMessage(messages.logs)}

-
-

- {intl.formatMessage(messages.logsDescription, { - code: function code(msg) { - return {msg}; - }, - configDir: appData.appDataPath, - })} -

-
-
- - - - - - - -
+ + + +
@@ -145,74 +164,47 @@ const SettingsLogs: React.FC = () => { - {data?.results.map( - ( - row: { - timestamp: string; - level: string; - label: string; - message: string; - }, - index: number - ) => { - return ( - - -
- - <> - -
-
- -
- {row.level === 'debug' && ( - - {row.level.toUpperCase()} - - )} - {row.level === 'info' && ( - - {row.level.toUpperCase()} - - )} - {row.level === 'warn' && ( - - {row.level.toUpperCase()} - - )} - {row.level === 'error' && ( - - {row.level.toUpperCase()} - - )} -
-
- -
- {row.label} -
-
- -
- {row.message} -
-
- - ); - } - )} + {data.results.map((row: LogMessage, index: number) => { + return ( + + + {intl.formatDate(row.timestamp, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: false, + })} + + + {row.level === 'debug' && ( + + {row.level.toUpperCase()} + + )} + {row.level === 'info' && ( + + {row.level.toUpperCase()} + + )} + {row.level === 'warn' && ( + + {row.level.toUpperCase()} + + )} + {row.level === 'error' && ( + + {row.level.toUpperCase()} + + )} + + {row.label} + {row.message} + + ); + })} {data.results.length === 0 && ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 1227f9b021..865aa1416a 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -464,10 +464,10 @@ "components.Settings.SettingsLogs.message": "Message", "components.Settings.SettingsLogs.next": "Next", "components.Settings.SettingsLogs.noresults": "No results.", - "components.Settings.SettingsLogs.pauseLogs": "Pause Logs", + "components.Settings.SettingsLogs.pauseLogs": "Pause", "components.Settings.SettingsLogs.previous": "Previous", "components.Settings.SettingsLogs.resultsperpage": "Display {pageSize} results per page", - "components.Settings.SettingsLogs.resumeLogs": "Resume Logs", + "components.Settings.SettingsLogs.resumeLogs": "Resume", "components.Settings.SettingsLogs.showall": "Show All Logs", "components.Settings.SettingsLogs.showingresults": "Showing {from} to {to} of {total} results", "components.Settings.SettingsLogs.time": "Timestamp", From 960e9a69913aa48e42b18b0591013617dc81a31c Mon Sep 17 00:00:00 2001 From: Danshil M Date: Wed, 17 Mar 2021 06:30:34 +0400 Subject: [PATCH 16/17] feat(settings): suggested changes --- overseerr-api.yml | 66 +++++++++---------- server/routes/settings/index.ts | 29 ++++---- .../Settings/SettingsLogs/index.tsx | 39 +++++------ src/i18n/locale/en.json | 2 +- 4 files changed, 65 insertions(+), 71 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index c175e18342..5d5bc035da 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -2252,37 +2252,6 @@ paths: responses: '204': description: 'Flushed cache' - /settings/notifications: - get: - summary: Return notification settings - description: Returns current notification settings in a JSON object. - tags: - - settings - responses: - '200': - description: Returned settings - content: - application/json: - schema: - $ref: '#/components/schemas/NotificationSettings' - post: - summary: Update notification settings - description: Updates notification settings with the provided values. - tags: - - settings - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NotificationSettings' - responses: - '200': - description: 'Values were sucessfully updated' - content: - application/json: - schema: - $ref: '#/components/schemas/NotificationSettings' /settings/logs: get: summary: Returns logs @@ -2321,16 +2290,47 @@ paths: properties: label: type: string - example: SERVER + example: server level: type: string example: info message: type: string - example: Server ready on port 3000 + example: Server ready on port 5055 timestamp: type: string example: 2020-12-15T16:20:00.069Z + /settings/notifications: + get: + summary: Return notification settings + description: Returns current notification settings in a JSON object. + tags: + - settings + responses: + '200': + description: Returned settings + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' + post: + summary: Update notification settings + description: Updates notification settings with the provided values. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' + responses: + '200': + description: 'Values were sucessfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationSettings' /settings/notifications/email: get: summary: Get email notification settings diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 90b437d6c0..993329edbf 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -273,31 +273,32 @@ settingsRoutes.get( const label = line.match(new RegExp(/[^\s]\[\w+\s*\w*\]/)) || []; const message = line.match(new RegExp(/:\s.*/)) || []; - 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, -1) : '', - }); + 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) : '', + }); + } }); - const filteredLogs = logs - .filter((row) => filter.includes(row.level)) - .reverse(); - - const displayedLogs = filteredLogs.slice(skip, skip + pageSize); + const displayedLogs = logs.reverse().slice(skip, skip + pageSize); return res.status(200).json({ pageInfo: { - pages: Math.ceil(filteredLogs.length / pageSize), + pages: Math.ceil(logs.length / pageSize), pageSize, - results: filteredLogs.length, + results: logs.length, page: Math.ceil(skip / pageSize) + 1, }, results: displayedLogs, } as LogsResultsResponse); } catch (error) { - logger.error(error.message, { label: 'Settings Router' }); + logger.error('Something went wrong while fetching the logs', { + label: 'Logs', + errorMessage: error.message, + }); return next({ status: 500, message: 'Something went wrong while fetching the logs', diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx index 183ffa134f..59a56890ce 100644 --- a/src/components/Settings/SettingsLogs/index.tsx +++ b/src/components/Settings/SettingsLogs/index.tsx @@ -5,6 +5,7 @@ import { LogMessage, LogsResultsResponse, } from '../../../../server/interfaces/api/settingsInterfaces'; +import Error from '../../../pages/_error'; import Badge from '../../Common/Badge'; import Button from '../../Common/Button'; import LoadingSpinner from '../../Common/LoadingSpinner'; @@ -84,7 +85,7 @@ const SettingsLogs: React.FC = () => { } if (!data) { - return ; + return ; } const hasNextPage = data.pageInfo.pages > pageIndex + 1; @@ -106,7 +107,7 @@ const SettingsLogs: React.FC = () => {
+ {intl.formatDate(row.timestamp, { year: 'numeric',