From f3a62e585f6161840361c53b9819e6ac62d28ade Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:04:05 +0100 Subject: [PATCH] Add log filters to web ui (#746) * Add max level select to logs Closes: #745 * Add log filter for server actions Closes: #744 * Respond to comments Change name of component, clean up onChange anon function --- .eslintrc.js | 2 +- packages/lib/src/data/composites.ts | 4 ++ packages/lib/src/data/messages_controller.ts | 6 +- .../web_ui/src/components/ControllerPage.tsx | 18 ++++-- .../web_ui/src/components/HostViewPage.tsx | 18 ++++-- .../src/components/InstanceViewPage.tsx | 31 ++++++++-- packages/web_ui/src/components/LogConsole.tsx | 59 ++++++++++++++++--- packages/web_ui/src/util/websocket.ts | 2 +- 8 files changed, 113 insertions(+), 27 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a2f84e90..d002fa69 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -191,7 +191,7 @@ module.exports = { "multiline-comment-style": ["error", "separate-lines"], "multiline-ternary": "off", "new-cap": ["error", { - "capIsNewExceptions": ["StringEnum"], + "capIsNewExceptions": ["StringEnum", "StringKey"], "capIsNewExceptionPattern": "^Type\\.", }], "new-parens": "error", diff --git a/packages/lib/src/data/composites.ts b/packages/lib/src/data/composites.ts index ccf28b9f..89f68d3c 100644 --- a/packages/lib/src/data/composites.ts +++ b/packages/lib/src/data/composites.ts @@ -4,6 +4,10 @@ export function StringEnum(values: [...T]) { return Type.Unsafe({ type: "string", enum: values }); } +export function StringKey(object: Record) { + return Type.Unsafe({ type: "string", enum: Object.keys(object) }); +} + export interface JSONDeserialisable { fromJSON(json: unknown): T; jsonSchema: object; diff --git a/packages/lib/src/data/messages_controller.ts b/packages/lib/src/data/messages_controller.ts index 41492305..cd51704a 100644 --- a/packages/lib/src/data/messages_controller.ts +++ b/packages/lib/src/data/messages_controller.ts @@ -1,5 +1,5 @@ import { Type, Static } from "@sinclair/typebox"; -import { JsonString, StringEnum, plainJson } from "./composites"; +import { JsonString, StringEnum, StringKey, plainJson } from "./composites"; import { levels } from "../logging"; import { ControllerConfig, HostConfig } from "../config"; @@ -217,12 +217,12 @@ export class LogMessageEvent { static dst = ["controller", "control"] as const; constructor( - public info: { level: string, message: string }, + public info: { level: keyof typeof levels, message: string }, ) { } static jsonSchema = Type.Object({ "info": Type.Object({ - "level": Type.String(), + "level": StringKey(levels), "message": Type.String(), }), }); diff --git a/packages/web_ui/src/components/ControllerPage.tsx b/packages/web_ui/src/components/ControllerPage.tsx index 9452e2c1..135b9e81 100644 --- a/packages/web_ui/src/components/ControllerPage.tsx +++ b/packages/web_ui/src/components/ControllerPage.tsx @@ -1,10 +1,10 @@ -import React, {useContext} from "react"; -import { Button, Descriptions, Popconfirm, Space, Typography } from "antd"; +import React, {useContext, useState} from "react"; +import { Button, Descriptions, Flex, Popconfirm, Space, Typography } from "antd"; import * as lib from "@clusterio/lib"; import PluginExtra from "./PluginExtra"; -import LogConsole from "./LogConsole"; +import LogConsole, { SelectMaxLogLevel } from "./LogConsole"; import { MetricCpuRatio, MetricCpuUsed, MetricMemoryRatio, MetricMemoryUsed, MetricDiskUsed, MetricDiskRatio, @@ -26,6 +26,7 @@ export default function ControllerPage() { const control = useContext(ControlContext); const [systems] = useSystems(); const system = systems.get("controller"); + const [maxLevel, setMaxLevel] = useState("info"); return {account.hasPermission("core.log.follow") && <> - Console - + + Console + + } {account.hasPermission("core.controller.get_config") && } diff --git a/packages/web_ui/src/components/HostViewPage.tsx b/packages/web_ui/src/components/HostViewPage.tsx index f70d9e2c..526a070e 100644 --- a/packages/web_ui/src/components/HostViewPage.tsx +++ b/packages/web_ui/src/components/HostViewPage.tsx @@ -1,5 +1,5 @@ -import React, { useContext } from "react"; -import { Descriptions, Spin, Tag, Typography, Button, Space, Modal, Popconfirm } from "antd"; +import React, { useContext, useState } from "react"; +import { Descriptions, Spin, Tag, Typography, Button, Space, Modal, Popconfirm, Flex } from "antd"; import { useParams } from "react-router-dom"; import * as lib from "@clusterio/lib"; @@ -7,7 +7,7 @@ import notify, { notifyErrorHandler } from "../util/notify"; import ControlContext from "./ControlContext"; import HostConfigTree from "./HostConfigTree"; import InstanceList from "./InstanceList"; -import LogConsole from "./LogConsole"; +import LogConsole, { SelectMaxLogLevel } from "./LogConsole"; import { useAccount } from "../model/account"; import { useInstances } from "../model/instance"; import { useHost } from "../model/host"; @@ -32,6 +32,7 @@ export default function HostViewPage() { const [systems] = useSystems(); const system = systems.get(hostId); const [host, synced] = useHost(hostId); + const [maxLevel, setMaxLevel] = useState("info"); const hostInstances = new Map([...instances].filter(([_id, instance]) => instance.assignedHost === hostId)); let nav = [{ name: "Hosts", path: "/hosts" }, { name: host?.name ?? String(hostId) }]; @@ -141,8 +142,15 @@ export default function HostViewPage() { } {account.hasPermission("core.log.follow") && <> - Console - + + Console + + } {account.hasPermission("core.host.get_config") && } diff --git a/packages/web_ui/src/components/InstanceViewPage.tsx b/packages/web_ui/src/components/InstanceViewPage.tsx index a7ada36c..b2ad9d8d 100644 --- a/packages/web_ui/src/components/InstanceViewPage.tsx +++ b/packages/web_ui/src/components/InstanceViewPage.tsx @@ -1,6 +1,6 @@ import React, { useContext, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { Alert, Button, Descriptions, Dropdown, MenuProps, Modal, Space, Spin, Typography } from "antd"; +import { Alert, Button, Descriptions, Dropdown, Flex, MenuProps, Modal, Space, Spin, Switch, Typography } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import DownOutlined from "@ant-design/icons/DownOutlined"; @@ -12,7 +12,7 @@ import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import InstanceConfigTree from "./InstanceConfigTree"; -import LogConsole from "./LogConsole"; +import LogConsole, { SelectMaxLogLevel } from "./LogConsole"; import InstanceRcon from "./InstanceRcon"; import AssignInstanceModal from "./AssignInstanceModal"; import StartStopInstanceButton from "./StartStopInstanceButton"; @@ -185,6 +185,8 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) { export default function InstanceViewPage() { let params = useParams(); let instanceId = Number(params.id); + const [maxLevel, setMaxLevel] = useState("server"); + const [actionsOnly, setActionsOnly] = useState(true); let navigate = useNavigate(); @@ -229,9 +231,30 @@ export default function InstanceViewPage() { } { account.hasAnyPermission("core.log.follow", "core.instance.send_rcon") - && Console + && + Console + { + account.hasPermission("core.log.follow") + && + + + } + + } + { + account.hasPermission("core.log.follow") + && } - {account.hasPermission("core.log.follow") && } { account.hasPermission("core.instance.send_rcon") && diff --git a/packages/web_ui/src/components/LogConsole.tsx b/packages/web_ui/src/components/LogConsole.tsx index 46ef5be6..f422a73c 100644 --- a/packages/web_ui/src/components/LogConsole.tsx +++ b/packages/web_ui/src/components/LogConsole.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useContext, useRef, useState, ReactElement } from "react"; -import { Typography } from "antd"; +import { Select, Typography } from "antd"; import * as lib from "@clusterio/lib"; @@ -51,10 +51,11 @@ function formatParsedOutput(parsed: Parsed, key: number) { } type Info = { - level: string; + level: keyof typeof lib.levels; message: string; parsed?: Parsed; }; + function formatLog(info: Info, key: number): ReactElement { if (info.level === "server" && info.parsed) { return formatParsedOutput(info.parsed, key); @@ -68,7 +69,30 @@ type LogConsoleProps = { controller?: boolean; hosts?: number[]; instances?: number[]; + maxLevel?: keyof typeof lib.levels; + actionsOnly?: boolean; }; + +export function SelectMaxLogLevel(props: { + value: keyof typeof lib.levels, + hidden?: (keyof typeof lib.levels)[], + onChange: (option: keyof typeof lib.levels) => void +}) { + return