Finish basic log viewer
This commit is contained in:
parent
6f2162d5f3
commit
c2a5451401
@ -38,6 +38,12 @@ class WebSocketHandler(logging.Handler):
|
|||||||
self.formatter = logging.Formatter()
|
self.formatter = logging.Formatter()
|
||||||
|
|
||||||
def emit(self, record: logging.LogRecord) -> None:
|
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)
|
# JSON conversion based on Marsel Mavletkulov's json-log-formatter (MIT license)
|
||||||
# https://github.com/marselester/json-log-formatter
|
# https://github.com/marselester/json-log-formatter
|
||||||
content = {
|
content = {
|
||||||
@ -45,6 +51,7 @@ class WebSocketHandler(logging.Handler):
|
|||||||
for name, value in record.__dict__.items()
|
for name, value in record.__dict__.items()
|
||||||
if name not in EXCLUDE_ATTRS
|
if name not in EXCLUDE_ATTRS
|
||||||
}
|
}
|
||||||
|
content["id"] = record.relativeCreated
|
||||||
content["msg"] = record.getMessage()
|
content["msg"] = record.getMessage()
|
||||||
content["time"] = datetime.utcnow()
|
content["time"] = datetime.utcnow()
|
||||||
|
|
||||||
@ -61,7 +68,7 @@ class WebSocketHandler(logging.Handler):
|
|||||||
try:
|
try:
|
||||||
await self.ws.send_json(record)
|
await self.ws.send_json(record)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
print("Log sending error:", e)
|
||||||
|
|
||||||
|
|
||||||
log_root = logging.getLogger("maubot")
|
log_root = logging.getLogger("maubot")
|
||||||
|
@ -82,6 +82,7 @@ export async function openLogSocket() {
|
|||||||
socket: null,
|
socket: null,
|
||||||
connected: false,
|
connected: false,
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
onLog: data => {},
|
||||||
fails: -1,
|
fails: -1,
|
||||||
}
|
}
|
||||||
const openHandler = () => {
|
const openHandler = () => {
|
||||||
@ -100,7 +101,9 @@ export async function openLogSocket() {
|
|||||||
console.info("Websocket connection authentication failed")
|
console.info("Websocket connection authentication failed")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
data.time = new Date(data.time)
|
||||||
console.log("SERVLOG", data)
|
console.log("SERVLOG", data)
|
||||||
|
wrapper.onLog(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const closeHandler = evt => {
|
const closeHandler = evt => {
|
||||||
|
@ -21,6 +21,7 @@ import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTab
|
|||||||
import Spinner from "../../components/Spinner"
|
import Spinner from "../../components/Spinner"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
import BaseMainView from "./BaseMainView"
|
import BaseMainView from "./BaseMainView"
|
||||||
|
import Log from "./Log"
|
||||||
|
|
||||||
const ClientListEntry = ({ entry }) => {
|
const ClientListEntry = ({ entry }) => {
|
||||||
const classes = ["client", "entry"]
|
const classes = ["client", "entry"]
|
||||||
@ -200,7 +201,8 @@ class Client extends BaseMainView {
|
|||||||
</>
|
</>
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="client">
|
return <>
|
||||||
|
<div className="client">
|
||||||
{this.renderSidebar()}
|
{this.renderSidebar()}
|
||||||
<div className="info">
|
<div className="info">
|
||||||
{this.renderPreferences()}
|
{this.renderPreferences()}
|
||||||
@ -208,6 +210,8 @@ class Client extends BaseMainView {
|
|||||||
{this.renderInstances()}
|
{this.renderInstances()}
|
||||||
</div>
|
</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 api from "../../api"
|
||||||
import Spinner from "../../components/Spinner"
|
import Spinner from "../../components/Spinner"
|
||||||
import BaseMainView from "./BaseMainView"
|
import BaseMainView from "./BaseMainView"
|
||||||
|
import Log from "./Log"
|
||||||
|
|
||||||
const InstanceListEntry = ({ entry }) => (
|
const InstanceListEntry = ({ entry }) => (
|
||||||
<NavLink className="instance entry" to={`/instance/${entry.id}`}>
|
<NavLink className="instance entry" to={`/instance/${entry.id}`}>
|
||||||
@ -167,6 +168,7 @@ class Instance extends BaseMainView {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="error">{this.state.error}</div>
|
<div className="error">{this.state.error}</div>
|
||||||
|
<Log showName={false} lines={this.props.log}/>
|
||||||
</div>
|
</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 Spinner from "../../components/Spinner"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
import BaseMainView from "./BaseMainView"
|
import BaseMainView from "./BaseMainView"
|
||||||
|
import Log from "./Log"
|
||||||
|
|
||||||
const PluginListEntry = ({ entry }) => (
|
const PluginListEntry = ({ entry }) => (
|
||||||
<NavLink className="plugin entry" to={`/plugin/${entry.id}`}>
|
<NavLink className="plugin entry" to={`/plugin/${entry.id}`}>
|
||||||
@ -90,6 +91,7 @@ class Plugin extends BaseMainView {
|
|||||||
</div>}
|
</div>}
|
||||||
<div className="error">{this.state.error}</div>
|
<div className="error">{this.state.error}</div>
|
||||||
{!this.isNew && this.renderInstances()}
|
{!this.isNew && this.renderInstances()}
|
||||||
|
<Log showName={false} lines={this.props.log}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { ReactComponent as Plus } from "../../res/plus.svg"
|
|||||||
import Instance from "./Instance"
|
import Instance from "./Instance"
|
||||||
import Client from "./Client"
|
import Client from "./Client"
|
||||||
import Plugin from "./Plugin"
|
import Plugin from "./Plugin"
|
||||||
|
import Home from "./Home"
|
||||||
|
|
||||||
class Dashboard extends Component {
|
class Dashboard extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -30,6 +31,8 @@ class Dashboard extends Component {
|
|||||||
plugins: {},
|
plugins: {},
|
||||||
sidebarOpen: false,
|
sidebarOpen: false,
|
||||||
}
|
}
|
||||||
|
this.logLines = []
|
||||||
|
this.logMap = {}
|
||||||
window.maubot = this
|
window.maubot = this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +58,13 @@ class Dashboard extends Component {
|
|||||||
plugins[plugin.id] = plugin
|
plugins[plugin.id] = plugin
|
||||||
}
|
}
|
||||||
this.setState({ instances, clients, plugins })
|
this.setState({ instances, clients, plugins })
|
||||||
|
|
||||||
const logs = await api.openLogSocket()
|
const logs = await api.openLogSocket()
|
||||||
console.log("WebSocket opened:", logs)
|
logs.onLog = data => {
|
||||||
window.logs = logs
|
this.logLines.push(data)
|
||||||
|
;(this.logMap[data.name] || (this.logMap[data.name] = [])).push(data)
|
||||||
|
this.setState({})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList(field, type) {
|
renderList(field, type) {
|
||||||
@ -85,11 +92,13 @@ class Dashboard extends Component {
|
|||||||
if (!entry) {
|
if (!entry) {
|
||||||
return this.renderNotFound(field.slice(0, -1))
|
return this.renderNotFound(field.slice(0, -1))
|
||||||
}
|
}
|
||||||
|
console.log(`maubot.${field.slice(0, -1)}.${id}`)
|
||||||
return React.createElement(type, {
|
return React.createElement(type, {
|
||||||
entry,
|
entry,
|
||||||
onDelete: () => this.delete(field, id),
|
onDelete: () => this.delete(field, id),
|
||||||
onChange: newEntry => this.add(field, newEntry, id),
|
onChange: newEntry => this.add(field, newEntry, id),
|
||||||
ctx: this.state,
|
ctx: this.state,
|
||||||
|
log: this.logMap[`maubot.${field.slice(0, -1)}.${id}`] || [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +151,7 @@ class Dashboard extends Component {
|
|||||||
|
|
||||||
<main className="view">
|
<main className="view">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" exact render={() => "Hello, World!"}/>
|
<Route path="/" exact render={() => <Home log={this.logLines}/>}/>
|
||||||
<Route path="/new/instance" render={() =>
|
<Route path="/new/instance" render={() =>
|
||||||
<Instance onChange={newEntry => this.add("instances", newEntry)}
|
<Instance onChange={newEntry => this.add("instances", newEntry)}
|
||||||
ctx={this.state}/>}/>
|
ctx={this.state}/>}/>
|
||||||
|
@ -78,17 +78,38 @@
|
|||||||
@import instance
|
@import instance
|
||||||
@import plugin
|
@import plugin
|
||||||
|
|
||||||
> .not-found
|
> div
|
||||||
text-align: center
|
|
||||||
margin-top: 5rem
|
|
||||||
font-size: 1.5rem
|
|
||||||
|
|
||||||
> div:not(.not-found)
|
|
||||||
margin: 2rem 4rem
|
margin: 2rem 4rem
|
||||||
|
|
||||||
@media screen and (max-width: 50rem)
|
@media screen and (max-width: 50rem)
|
||||||
margin: 2rem 1rem
|
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
|
div.buttons
|
||||||
+button-group
|
+button-group
|
||||||
display: flex
|
display: flex
|
||||||
|
Loading…
Reference in New Issue
Block a user