From ac276868fb8e0e942ee694ee8223c13663080482 Mon Sep 17 00:00:00 2001 From: ze0s <43699394+zze0s@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:37:06 +0100 Subject: [PATCH] feat(settings): make log level configurable from UI (#704) * feat(settings): set log level * fix: light theme colors * fix: light theme colors size unit --- internal/config/config.go | 38 ++++- internal/config/config_test.go | 18 +-- internal/http/config.go | 12 ++ web/src/domain/constants.ts | 3 + web/src/domain/routes.tsx | 22 +-- web/src/screens/Settings.tsx | 4 +- web/src/screens/settings/Application.tsx | 32 +++- web/src/screens/settings/Irc.tsx | 4 +- web/src/screens/settings/Logs.tsx | 190 +++++++++++++++++++++++ web/src/screens/settings/index.ts | 9 ++ web/src/types/Config.d.ts | 8 +- 11 files changed, 310 insertions(+), 30 deletions(-) create mode 100644 web/src/screens/settings/Logs.tsx create mode 100644 web/src/screens/settings/index.ts diff --git a/internal/config/config.go b/internal/config/config.go index 01c51d6..feb30c0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -281,7 +281,9 @@ func (c *AppConfig) UpdateConfig() error { func (c *AppConfig) processLines(lines []string) []string { // keep track of not found values to append at bottom var ( - foundLineUpdate = false + foundLineUpdate = false + foundLineLogLevel = false + foundLineLogPath = false ) for i, line := range lines { @@ -290,6 +292,18 @@ func (c *AppConfig) processLines(lines []string) []string { lines[i] = fmt.Sprintf("checkForUpdates = %t", c.Config.CheckForUpdates) foundLineUpdate = true } + if !foundLineLogLevel && strings.Contains(line, "logLevel =") { + lines[i] = fmt.Sprintf(`logLevel = "%s"`, c.Config.LogLevel) + foundLineLogLevel = true + } + if !foundLineLogPath && strings.Contains(line, "logPath =") { + if c.Config.LogPath == "" { + lines[i] = `#logPath = ""` + } else { + lines[i] = fmt.Sprintf("logPath = \"%s\"", c.Config.LogPath) + } + foundLineLogPath = true + } } // append missing vars to bottom @@ -299,5 +313,27 @@ func (c *AppConfig) processLines(lines []string) []string { lines = append(lines, fmt.Sprintf("checkForUpdates = %t", c.Config.CheckForUpdates)) } + if !foundLineLogLevel { + lines = append(lines, "# Log level") + lines = append(lines, "#") + lines = append(lines, `# Default: "DEBUG"`) + lines = append(lines, "#") + lines = append(lines, `# Options: "ERROR", "DEBUG", "INFO", "WARN", "TRACE"`) + lines = append(lines, "#") + lines = append(lines, fmt.Sprintf(`logLevel = "%s"`, c.Config.LogLevel)) + } + + if !foundLineLogPath { + lines = append(lines, "# Log Path") + lines = append(lines, "#") + lines = append(lines, "# Optional") + lines = append(lines, "#") + if c.Config.LogPath == "" { + lines = append(lines, `#logPath = ""`) + } else { + lines = append(lines, fmt.Sprintf(`logPath = "%s"`, c.Config.LogPath)) + } + } + return lines } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e1e8d97..d7cb465 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,10 +1,11 @@ package config import ( - "reflect" "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/autobrr/autobrr/internal/domain" ) @@ -25,20 +26,20 @@ func TestAppConfig_processLines(t *testing.T) { { name: "append missing", fields: fields{ - Config: &domain.Config{CheckForUpdates: true}, + Config: &domain.Config{CheckForUpdates: true, LogLevel: "TRACE"}, m: sync.Mutex{}, }, args: args{[]string{}}, - want: []string{"# Check for updates", "#", "checkForUpdates = true"}, + want: []string{"# Check for updates", "#", "checkForUpdates = true", "# Log level", "#", "# Default: \"DEBUG\"", "#", "# Options: \"ERROR\", \"DEBUG\", \"INFO\", \"WARN\", \"TRACE\"", "#", `logLevel = "TRACE"`, "# Log Path", "#", "# Optional", "#", "#logPath = \"\""}, }, { name: "update existing", fields: fields{ - Config: &domain.Config{CheckForUpdates: true}, + Config: &domain.Config{CheckForUpdates: true, LogLevel: "TRACE"}, m: sync.Mutex{}, }, - args: args{[]string{"# Check for updates", "#", "#checkForUpdates = false"}}, - want: []string{"# Check for updates", "#", "checkForUpdates = true"}, + args: args{[]string{"# Check for updates", "#", "checkForUpdates = false", "# Log level", "#", "# Default: \"DEBUG\"", "#", "# Options: \"ERROR\", \"DEBUG\", \"INFO\", \"WARN\", \"TRACE\"", "#", `logLevel = "TRACE"`, "# Log Path", "#", "# Optional", "#", "#logPath = \"\""}}, + want: []string{"# Check for updates", "#", "checkForUpdates = true", "# Log level", "#", "# Default: \"DEBUG\"", "#", "# Options: \"ERROR\", \"DEBUG\", \"INFO\", \"WARN\", \"TRACE\"", "#", `logLevel = "TRACE"`, "# Log Path", "#", "# Optional", "#", "#logPath = \"\""}, }, } for _, tt := range tests { @@ -47,9 +48,8 @@ func TestAppConfig_processLines(t *testing.T) { Config: tt.fields.Config, m: tt.fields.m, } - if got := c.processLines(tt.args.lines); !reflect.DeepEqual(got, tt.want) { - t.Errorf("processLines() = %v, want %v", got, tt.want) - } + + assert.Equalf(t, tt.want, c.processLines(tt.args.lines), tt.name) }) } } diff --git a/internal/http/config.go b/internal/http/config.go index 65d2379..651cf6e 100644 --- a/internal/http/config.go +++ b/internal/http/config.go @@ -16,6 +16,8 @@ type configJson struct { Port int `json:"port"` LogLevel string `json:"log_level"` LogPath string `json:"log_path"` + LogMaxSize int `json:"log_max_size"` + LogMaxBackups int `json:"log_max_backups"` BaseURL string `json:"base_url"` CheckForUpdates bool `json:"check_for_updates"` Version string `json:"version"` @@ -49,6 +51,8 @@ func (h configHandler) getConfig(w http.ResponseWriter, r *http.Request) { Port: h.cfg.Config.Port, LogLevel: h.cfg.Config.LogLevel, LogPath: h.cfg.Config.LogPath, + LogMaxSize: h.cfg.Config.LogMaxSize, + LogMaxBackups: h.cfg.Config.LogMaxBackups, BaseURL: h.cfg.Config.BaseURL, CheckForUpdates: h.cfg.Config.CheckForUpdates, Version: h.server.version, @@ -71,6 +75,14 @@ func (h configHandler) updateConfig(w http.ResponseWriter, r *http.Request) { h.cfg.Config.CheckForUpdates = *data.CheckForUpdates } + if data.LogLevel != nil { + h.cfg.Config.LogLevel = *data.LogLevel + } + + if data.LogPath != nil { + h.cfg.Config.LogPath = *data.LogPath + } + if err := h.cfg.UpdateConfig(); err != nil { render.Status(r, http.StatusInternalServerError) render.JSON(w, r, errorResponse{ diff --git a/web/src/domain/constants.ts b/web/src/domain/constants.ts index e52fb4f..5159e8a 100644 --- a/web/src/domain/constants.ts +++ b/web/src/domain/constants.ts @@ -422,6 +422,9 @@ export const DownloadRuleConditionOptions: OptionBasic[] = [ } ]; +const logLevel = ["DEBUG", "INFO", "WARN", "ERROR", "TRACE"] as const; + +export const LogLevelOptions = logLevel.map(v => ({ value: v, label: v, key: v })); export interface SelectOption { label: string; diff --git a/web/src/domain/routes.tsx b/web/src/domain/routes.tsx index a7048c3..1f94394 100644 --- a/web/src/domain/routes.tsx +++ b/web/src/domain/routes.tsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; import { Login } from "../screens/auth/login"; import { Logout } from "../screens/auth/logout"; @@ -9,15 +9,18 @@ import { FilterDetails, Filters } from "../screens/filters"; import { Logs } from "../screens/Logs"; import { Releases } from "../screens/releases"; import Settings from "../screens/Settings"; -import ApplicationSettings from "../screens/settings/Application"; -import DownloadClientSettings from "../screens/settings/DownloadClient"; -import FeedSettings from "../screens/settings/Feed"; -import IndexerSettings from "../screens/settings/Indexer"; -import { IrcSettings } from "../screens/settings/Irc"; -import NotificationSettings from "../screens/settings/Notifications"; +import { + APISettings, + ApplicationSettings, + DownloadClientSettings, + FeedSettings, + IndexerSettings, + IrcSettings, + LogSettings, + NotificationSettings, + ReleaseSettings +} from "../screens/settings"; import { RegexPlayground } from "../screens/settings/RegexPlayground"; -import ReleaseSettings from "../screens/settings/Releases"; -import APISettings from "../screens/settings/Api"; import { baseUrl } from "../utils"; @@ -36,6 +39,7 @@ export const LocalRouter = ({ isLoggedIn }: { isLoggedIn: boolean }) => ( }> } /> + } /> } /> } /> } /> diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx index dccc8a1..86cc401 100644 --- a/web/src/screens/Settings.tsx +++ b/web/src/screens/Settings.tsx @@ -6,7 +6,8 @@ import { FolderArrowDownIcon, KeyIcon, RectangleStackIcon, - RssIcon + RssIcon, + Square3Stack3DIcon } from "@heroicons/react/24/outline"; import { classNames } from "../utils"; @@ -19,6 +20,7 @@ interface NavTabType { const subNavigation: NavTabType[] = [ { name: "Application", href: "", icon: CogIcon }, + { name: "Logs", href: "logs", icon: Square3Stack3DIcon }, { name: "Indexers", href: "indexers", icon: KeyIcon }, { name: "IRC", href: "irc", icon: ChatBubbleLeftRightIcon }, { name: "Feeds", href: "feeds", icon: RssIcon }, diff --git a/web/src/screens/settings/Application.tsx b/web/src/screens/settings/Application.tsx index 6d5bca0..1e2d91e 100644 --- a/web/src/screens/settings/Application.tsx +++ b/web/src/screens/settings/Application.tsx @@ -11,18 +11,37 @@ interface RowItemProps { label: string; value?: string; title?: string; + emptyText?: string; newUpdate?: GithubRelease; } -const RowItem = ({ label, value, title }: RowItemProps) => { - if (!value) - return null; - +const RowItem = ({ label, value, title, emptyText }: RowItemProps) => { return (
{label}:
- {value} + {value ? value : emptyText} +
+
+ ); +}; + +interface RowItemNumberProps { + label: string; + value?: string | number; + title?: string; + unit?: string; +} + +const RowItemNumber = ({ label, value, title, unit }: RowItemNumberProps) => { + return ( +
+
{label}:
+
+ {value} + {unit && + {unit} + }
); @@ -157,8 +176,6 @@ function ApplicationSettings() { - -
    @@ -196,6 +213,7 @@ function ApplicationSettings() {
+ ); } diff --git a/web/src/screens/settings/Irc.tsx b/web/src/screens/settings/Irc.tsx index 9e0df98..189113d 100644 --- a/web/src/screens/settings/Irc.tsx +++ b/web/src/screens/settings/Irc.tsx @@ -21,7 +21,7 @@ import { TrashIcon } from "@heroicons/react/24/outline"; -export const IrcSettings = () => { +const IrcSettings = () => { const [expandNetworks, toggleExpand] = useToggle(false); const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false); @@ -490,3 +490,5 @@ const ListItemDropdown = ({ ); }; + +export default IrcSettings; diff --git a/web/src/screens/settings/Logs.tsx b/web/src/screens/settings/Logs.tsx new file mode 100644 index 0000000..5f2348e --- /dev/null +++ b/web/src/screens/settings/Logs.tsx @@ -0,0 +1,190 @@ +import { useMutation, useQuery } from "react-query"; +import { APIClient } from "../../api/APIClient"; +import { GithubRelease } from "../../types/Update"; +import { toast } from "react-hot-toast"; +import Toast from "../../components/notifications/Toast"; +import { queryClient } from "../../App"; +import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; +import { LogLevelOptions, SelectOption } from "../../domain/constants"; + +interface RowItemProps { + label: string; + value?: string; + title?: string; + emptyText?: string; + newUpdate?: GithubRelease; +} + +const RowItem = ({ label, value, title, emptyText }: RowItemProps) => { + return ( +
+
{label}:
+
+ {value ? value : emptyText} +
+
+ ); +}; + +interface RowItemNumberProps { + label: string; + value?: string | number; + title?: string; + unit?: string; +} + +const RowItemNumber = ({ label, value, title, unit }: RowItemNumberProps) => { + return ( +
+
{label}:
+
+ {value} + {unit && + {unit} + } +
+
+ ); +}; + +const Input = (props: InputProps) => { + return ( + + ); +}; + +const Control = (props: ControlProps) => { + return ( + + ); +}; + +const Menu = (props: MenuProps) => { + return ( + + ); +}; + +const Option = (props: OptionProps) => { + return ( + + ); +}; + +const RowItemSelect = ({ id, title, label, value, options, onChange }: any) => { + return ( +
+
{label}:
+
+