Finish basic log viewer
This commit is contained in:
parent
6f2162d5f3
commit
c2a5451401
9 changed files with 124 additions and 17 deletions
|
@ -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")
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 <div className="client">
|
||||
{this.renderSidebar()}
|
||||
<div className="info">
|
||||
{this.renderPreferences()}
|
||||
{this.renderPrefButtons()}
|
||||
{this.renderInstances()}
|
||||
return <>
|
||||
<div className="client">
|
||||
{this.renderSidebar()}
|
||||
<div className="info">
|
||||
{this.renderPreferences()}
|
||||
{this.renderPrefButtons()}
|
||||
{this.renderInstances()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Log showName={false} lines={this.props.log}/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
maubot/management/frontend/src/pages/dashboard/Home.js
Normal file
30
maubot/management/frontend/src/pages/dashboard/Home.js
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
import React, { Component } from "react"
|
||||
import Log from "./Log"
|
||||
|
||||
class Home extends Component {
|
||||
render() {
|
||||
return <>
|
||||
<div className="home">
|
||||
See sidebar to get started
|
||||
</div>
|
||||
<Log lines={this.props.log}/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export default Home
|
|
@ -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 }) => (
|
||||
<NavLink className="instance entry" to={`/instance/${entry.id}`}>
|
||||
|
@ -167,6 +168,7 @@ class Instance extends BaseMainView {
|
|||
</button>
|
||||
</div>
|
||||
<div className="error">{this.state.error}</div>
|
||||
<Log showName={false} lines={this.props.log}/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
29
maubot/management/frontend/src/pages/dashboard/Log.js
Normal file
29
maubot/management/frontend/src/pages/dashboard/Log.js
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
import React from "react"
|
||||
|
||||
const Log = ({ lines, showName = true }) => <div className="log">
|
||||
{lines.map(data =>
|
||||
<div className="row" key={data.id}>
|
||||
<span className="time">{data.time.toLocaleTimeString()}</span>
|
||||
<span className="level">{data.levelname}</span>
|
||||
{showName && <span className="logger">{data.name}</span>}
|
||||
<span className="text">{data.msg}</span>
|
||||
</div>,
|
||||
)}
|
||||
</div>
|
||||
|
||||
export default Log
|
|
@ -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 }) => (
|
||||
<NavLink className="plugin entry" to={`/plugin/${entry.id}`}>
|
||||
|
@ -90,6 +91,7 @@ class Plugin extends BaseMainView {
|
|||
</div>}
|
||||
<div className="error">{this.state.error}</div>
|
||||
{!this.isNew && this.renderInstances()}
|
||||
<Log showName={false} lines={this.props.log}/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
|
||||
<main className="view">
|
||||
<Switch>
|
||||
<Route path="/" exact render={() => "Hello, World!"}/>
|
||||
<Route path="/" exact render={() => <Home log={this.logLines}/>}/>
|
||||
<Route path="/new/instance" render={() =>
|
||||
<Instance onChange={newEntry => this.add("instances", newEntry)}
|
||||
ctx={this.state}/>}/>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue