From c2a54514018dd3630604e93cb2886a6bbb81a884 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 12 Nov 2018 00:00:37 +0200 Subject: [PATCH] Finish basic log viewer --- maubot/management/api/log.py | 9 ++++- maubot/management/frontend/src/api.js | 3 ++ .../frontend/src/pages/dashboard/Client.js | 18 ++++++---- .../frontend/src/pages/dashboard/Home.js | 30 +++++++++++++++++ .../frontend/src/pages/dashboard/Instance.js | 2 ++ .../frontend/src/pages/dashboard/Log.js | 29 ++++++++++++++++ .../frontend/src/pages/dashboard/Plugin.js | 2 ++ .../frontend/src/pages/dashboard/index.js | 15 +++++++-- .../frontend/src/style/pages/dashboard.sass | 33 +++++++++++++++---- 9 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 maubot/management/frontend/src/pages/dashboard/Home.js create mode 100644 maubot/management/frontend/src/pages/dashboard/Log.js diff --git a/maubot/management/api/log.py b/maubot/management/api/log.py index 467f16e..572bf87 100644 --- a/maubot/management/api/log.py +++ b/maubot/management/api/log.py @@ -38,6 +38,12 @@ class WebSocketHandler(logging.Handler): self.formatter = logging.Formatter() def emit(self, record: logging.LogRecord) -> None: + try: + self._emit(record) + except Exception as e: + print("Logging error:", e) + + def _emit(self, record: logging.LogRecord) -> None: # JSON conversion based on Marsel Mavletkulov's json-log-formatter (MIT license) # https://github.com/marselester/json-log-formatter content = { @@ -45,6 +51,7 @@ class WebSocketHandler(logging.Handler): for name, value in record.__dict__.items() if name not in EXCLUDE_ATTRS } + content["id"] = record.relativeCreated content["msg"] = record.getMessage() content["time"] = datetime.utcnow() @@ -61,7 +68,7 @@ class WebSocketHandler(logging.Handler): try: await self.ws.send_json(record) except Exception as e: - pass + print("Log sending error:", e) log_root = logging.getLogger("maubot") diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index fc4e518..0992233 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -82,6 +82,7 @@ export async function openLogSocket() { socket: null, connected: false, authenticated: false, + onLog: data => {}, fails: -1, } const openHandler = () => { @@ -100,7 +101,9 @@ export async function openLogSocket() { console.info("Websocket connection authentication failed") } } else { + data.time = new Date(data.time) console.log("SERVLOG", data) + wrapper.onLog(data) } } const closeHandler = evt => { diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js index 0424911..45bf10e 100644 --- a/maubot/management/frontend/src/pages/dashboard/Client.js +++ b/maubot/management/frontend/src/pages/dashboard/Client.js @@ -21,6 +21,7 @@ import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTab import Spinner from "../../components/Spinner" import api from "../../api" import BaseMainView from "./BaseMainView" +import Log from "./Log" const ClientListEntry = ({ entry }) => { const classes = ["client", "entry"] @@ -200,14 +201,17 @@ class Client extends BaseMainView { render() { - return
- {this.renderSidebar()} -
- {this.renderPreferences()} - {this.renderPrefButtons()} - {this.renderInstances()} + return <> +
+ {this.renderSidebar()} +
+ {this.renderPreferences()} + {this.renderPrefButtons()} + {this.renderInstances()} +
-
+ + } } diff --git a/maubot/management/frontend/src/pages/dashboard/Home.js b/maubot/management/frontend/src/pages/dashboard/Home.js new file mode 100644 index 0000000..f1a4fad --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/Home.js @@ -0,0 +1,30 @@ +// maubot - A plugin-based Matrix bot system. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React, { Component } from "react" +import Log from "./Log" + +class Home extends Component { + render() { + return <> +
+ See sidebar to get started +
+ + + } +} + +export default Home diff --git a/maubot/management/frontend/src/pages/dashboard/Instance.js b/maubot/management/frontend/src/pages/dashboard/Instance.js index 8f1ed42..58463be 100644 --- a/maubot/management/frontend/src/pages/dashboard/Instance.js +++ b/maubot/management/frontend/src/pages/dashboard/Instance.js @@ -23,6 +23,7 @@ import PrefTable, { PrefInput, PrefSelect, PrefSwitch } from "../../components/P import api from "../../api" import Spinner from "../../components/Spinner" import BaseMainView from "./BaseMainView" +import Log from "./Log" const InstanceListEntry = ({ entry }) => ( @@ -167,6 +168,7 @@ class Instance extends BaseMainView {
{this.state.error}
+ } } diff --git a/maubot/management/frontend/src/pages/dashboard/Log.js b/maubot/management/frontend/src/pages/dashboard/Log.js new file mode 100644 index 0000000..9b1929c --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/Log.js @@ -0,0 +1,29 @@ +// maubot - A plugin-based Matrix bot system. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React from "react" + +const Log = ({ lines, showName = true }) =>
+ {lines.map(data => +
+ {data.time.toLocaleTimeString()} + {data.levelname} + {showName && {data.name}} + {data.msg} +
, + )} +
+ +export default Log diff --git a/maubot/management/frontend/src/pages/dashboard/Plugin.js b/maubot/management/frontend/src/pages/dashboard/Plugin.js index aac38cf..4ca7f6a 100644 --- a/maubot/management/frontend/src/pages/dashboard/Plugin.js +++ b/maubot/management/frontend/src/pages/dashboard/Plugin.js @@ -21,6 +21,7 @@ import PrefTable, { PrefInput } from "../../components/PreferenceTable" import Spinner from "../../components/Spinner" import api from "../../api" import BaseMainView from "./BaseMainView" +import Log from "./Log" const PluginListEntry = ({ entry }) => ( @@ -90,6 +91,7 @@ class Plugin extends BaseMainView { }
{this.state.error}
{!this.isNew && this.renderInstances()} + } } diff --git a/maubot/management/frontend/src/pages/dashboard/index.js b/maubot/management/frontend/src/pages/dashboard/index.js index 26b70fa..232923e 100644 --- a/maubot/management/frontend/src/pages/dashboard/index.js +++ b/maubot/management/frontend/src/pages/dashboard/index.js @@ -20,6 +20,7 @@ import { ReactComponent as Plus } from "../../res/plus.svg" import Instance from "./Instance" import Client from "./Client" import Plugin from "./Plugin" +import Home from "./Home" class Dashboard extends Component { constructor(props) { @@ -30,6 +31,8 @@ class Dashboard extends Component { plugins: {}, sidebarOpen: false, } + this.logLines = [] + this.logMap = {} window.maubot = this } @@ -55,9 +58,13 @@ class Dashboard extends Component { plugins[plugin.id] = plugin } this.setState({ instances, clients, plugins }) + const logs = await api.openLogSocket() - console.log("WebSocket opened:", logs) - window.logs = logs + logs.onLog = data => { + this.logLines.push(data) + ;(this.logMap[data.name] || (this.logMap[data.name] = [])).push(data) + this.setState({}) + } } renderList(field, type) { @@ -85,11 +92,13 @@ class Dashboard extends Component { if (!entry) { return this.renderNotFound(field.slice(0, -1)) } + console.log(`maubot.${field.slice(0, -1)}.${id}`) return React.createElement(type, { entry, onDelete: () => this.delete(field, id), onChange: newEntry => this.add(field, newEntry, id), ctx: this.state, + log: this.logMap[`maubot.${field.slice(0, -1)}.${id}`] || [], }) } @@ -142,7 +151,7 @@ class Dashboard extends Component {
- "Hello, World!"}/> + }/> this.add("instances", newEntry)} ctx={this.state}/>}/> diff --git a/maubot/management/frontend/src/style/pages/dashboard.sass b/maubot/management/frontend/src/style/pages/dashboard.sass index 31f15bb..3ab03fa 100644 --- a/maubot/management/frontend/src/style/pages/dashboard.sass +++ b/maubot/management/frontend/src/style/pages/dashboard.sass @@ -78,17 +78,38 @@ @import instance @import plugin - > .not-found - text-align: center - margin-top: 5rem - font-size: 1.5rem - - > div:not(.not-found) + > div margin: 2rem 4rem @media screen and (max-width: 50rem) margin: 2rem 1rem + div.log > div.row + span.level, span.logger + display: none + + > div.not-found, > div.home + text-align: center + margin-top: 5rem + font-size: 1.5rem + + div.log + text-align: left + font-size: 12px + max-height: 20rem + font-family: "Fira Code", monospace + overflow: auto + + > div.row + white-space: nowrap + + > span.level:before + content: " [" + > span.logger:before + content: "@" + > span.text:before + content: "] " + div.buttons +button-group display: flex