+
+ View logs
+
>
}
}
diff --git a/maubot/management/frontend/src/pages/dashboard/Instance.js b/maubot/management/frontend/src/pages/dashboard/Instance.js
index 37596de..5ea2d2b 100644
--- a/maubot/management/frontend/src/pages/dashboard/Instance.js
+++ b/maubot/management/frontend/src/pages/dashboard/Instance.js
@@ -167,8 +167,8 @@ class Instance extends BaseMainView {
{this.state.saving ? : (this.isNew ? "Create" : "Save")}
+ {this.renderLogButton(`instance.${this.state.id}`)}
{this.state.error}
- {this.renderLog()}
}
}
diff --git a/maubot/management/frontend/src/pages/dashboard/Log.js b/maubot/management/frontend/src/pages/dashboard/Log.js
index 55b5b86..7e710f9 100644
--- a/maubot/management/frontend/src/pages/dashboard/Log.js
+++ b/maubot/management/frontend/src/pages/dashboard/Log.js
@@ -13,20 +13,116 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import React from "react"
+import React, { PureComponent } from "react"
+import { Link } from "react-router-dom"
+import JSONTree from "react-json-tree"
+import api from "../../api"
+import Modal from "./Modal"
-const Log = ({ lines, showName = true }) =>
- {lines.map(data => <>
-
- {data.time.toLocaleTimeString()}
- {data.levelname}
- {showName && {data.name} }
- {data.msg}
-
- {data.exc_info &&
- {data.exc_info.replace(/\\n/g, "\n")}
-
}
- >)}
-
+class LogEntry extends PureComponent {
+ static contextType = Modal.Context
+
+ renderName() {
+ const line = this.props.line
+ if (line.nameLink) {
+ const modal = this.context
+ return (
+
+ {line.name}
+
+ )
+ }
+ return line.name
+ }
+
+ renderContent() {
+ if (this.props.line.matrix_http_request) {
+ const req = this.props.line.matrix_http_request
+
+ return <>
+ {req.method} {req.path}
+
+ {Object.entries(req.content).length > 0
+ && }
+
+ >
+ }
+ return this.props.line.msg
+ }
+
+ renderTime() {
+ return this.props.line.time.toLocaleTimeString("en-GB")
+ }
+
+ renderLevelName() {
+ return this.props.line.levelname
+ }
+
+ get unfocused() {
+ return this.props.focus && this.props.line.name !== this.props.focus
+ ? "unfocused"
+ : ""
+ }
+
+ renderRow(content) {
+ return (
+
+ {this.renderTime()}
+ {this.renderLevelName()}
+ {this.renderName()}
+ {content}
+
+ )
+ }
+
+ renderExceptionInfo() {
+ if (!api.debugOpenFileEnabled()) {
+ return this.props.line.exc_info
+ }
+ const fileLinks = []
+ let str = this.props.line.exc_info.replace(
+ /File "(.+)", line ([0-9]+), in (.+)/g,
+ (_, file, line, method) => {
+ fileLinks.push(
+ {
+ api.debugOpenFile(file, line)
+ return false
+ }}>File "{file}", line {line}, in {method} ,
+ )
+ return "||EDGE||"
+ })
+ fileLinks.reverse()
+
+ const result = []
+ let key = 0
+ for (const part of str.split("||EDGE||")) {
+ result.push(
+ {part}
+ {fileLinks.pop()}
+ )
+ }
+ return result
+ }
+
+ render() {
+ return <>
+ {this.renderRow(this.renderContent())}
+ {this.props.line.exc_info && this.renderRow(this.renderExceptionInfo())}
+ >
+ }
+}
+
+class Log extends PureComponent {
+ render() {
+ return (
+
+
+ {this.props.lines.map(data => )}
+
+
+ )
+ }
+}
export default Log
diff --git a/maubot/management/frontend/src/pages/dashboard/Modal.js b/maubot/management/frontend/src/pages/dashboard/Modal.js
new file mode 100644
index 0000000..9bfc48e
--- /dev/null
+++ b/maubot/management/frontend/src/pages/dashboard/Modal.js
@@ -0,0 +1,51 @@
+// 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, createContext } from "react"
+
+const rem = 16
+
+class Modal extends Component {
+ static Context = createContext(null)
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ open: false,
+ }
+ this.wrapper = { clientWidth: 9001 }
+ }
+
+ open = () => this.setState({ open: true })
+ close = () => this.setState({ open: false })
+
+ render() {
+ return this.state.open && (
+ this.wrapper = ref}
+ onClick={() => this.wrapper.clientWidth > 45 * rem && this.close()}>
+
evt.stopPropagation()}>
+
Close
+
+
+ {this.props.children}
+
+
+
+
+ )
+ }
+}
+
+export default Modal
diff --git a/maubot/management/frontend/src/pages/dashboard/Plugin.js b/maubot/management/frontend/src/pages/dashboard/Plugin.js
index b29ec2d..e3ea809 100644
--- a/maubot/management/frontend/src/pages/dashboard/Plugin.js
+++ b/maubot/management/frontend/src/pages/dashboard/Plugin.js
@@ -93,9 +93,9 @@ class Plugin extends BaseMainView {
{this.state.deleting ? : "Delete"}
}
+ {this.renderLogButton("loader.zip")}
{this.state.error}
{this.renderInstances()}
- {this.renderLog()}
}
}
diff --git a/maubot/management/frontend/src/pages/dashboard/index.js b/maubot/management/frontend/src/pages/dashboard/index.js
index ec4ad7d..c042fe8 100644
--- a/maubot/management/frontend/src/pages/dashboard/index.js
+++ b/maubot/management/frontend/src/pages/dashboard/index.js
@@ -21,6 +21,8 @@ import Instance from "./Instance"
import Client from "./Client"
import Plugin from "./Plugin"
import Home from "./Home"
+import Log from "./Log"
+import Modal from "./Modal"
class Dashboard extends Component {
constructor(props) {
@@ -30,9 +32,14 @@ class Dashboard extends Component {
clients: {},
plugins: {},
sidebarOpen: false,
+ modalOpen: false,
+ logFocus: "",
}
this.logLines = []
this.logMap = {}
+ this.logModal = {
+ open: () => undefined,
+ }
window.maubot = this
}
@@ -44,7 +51,8 @@ class Dashboard extends Component {
async componentWillMount() {
const [instanceList, clientList, pluginList] = await Promise.all([
- api.getInstances(), api.getClients(), api.getPlugins()])
+ api.getInstances(), api.getClients(), api.getPlugins(),
+ api.updateDebugOpenFileEnabled()])
const instances = {}
for (const instance of instanceList) {
instances[instance.id] = instance
@@ -60,10 +68,32 @@ class Dashboard extends Component {
this.setState({ instances, clients, plugins })
const logs = await api.openLogSocket()
+
+ const processEntry = (entry) => {
+ entry.time = new Date(entry.time)
+ if (entry.name.startsWith("maubot.")) {
+ entry.name = entry.name.substr("maubot.".length)
+ }
+ if (entry.name.startsWith("client.")) {
+ entry.name = entry.name.substr("client.".length)
+ entry.nameLink = `/client/${entry.name}`
+ } else if (entry.name.startsWith("instance.")) {
+ entry.nameLink = `/instance/${entry.name.substr("instance.".length)}`
+ }
+ (this.logMap[entry.name] || (this.logMap[entry.name] = [])).push(entry)
+ }
+
+ logs.onHistory = history => {
+ for (const data of history) {
+ processEntry(data)
+ }
+ this.logLines = history
+ this.setState({ logFocus: this.state.logFocus })
+ }
logs.onLog = data => {
+ processEntry(data)
this.logLines.push(data)
- ;(this.logMap[data.name] || (this.logMap[data.name] = [])).push(data)
- this.setState({})
+ this.setState({ logFocus: this.state.logFocus })
}
}
@@ -87,28 +117,22 @@ class Dashboard extends Component {
this.setState({ [stateField]: data })
}
- getLog(field, id) {
- if (field === "clients") {
- return this.logMap[`maubot.client.${id}`]
- } else if (field === "instances") {
- return this.logMap[`maubot.plugin.${id}`]
- } else if (field === "plugins") {
- return this.logMap["maubot.loader.zip"]
- }
- }
-
renderView(field, type, id) {
const entry = this.state[field][id]
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),
+ openLog: filter => {
+ this.setState({
+ logFocus: filter,
+ })
+ this.logModal.open()
+ },
ctx: this.state,
- log: this.getLog(field, id) || [],
})
}
@@ -118,7 +142,7 @@ class Dashboard extends Component {
)
- render() {
+ renderMain() {
return
@@ -161,7 +185,7 @@ class Dashboard extends Component {
- }/>
+ }/>
this.add("instances", newEntry)}
ctx={this.state}/>}/>
@@ -180,6 +204,19 @@ class Dashboard extends Component {
}
+
+ renderModal() {
+ return this.logModal = ref}>
+
+
+ }
+
+ render() {
+ return <>
+ {this.renderMain()}
+ {this.renderModal()}
+ >
+ }
}
export default withRouter(Dashboard)
diff --git a/maubot/management/frontend/src/style/index.sass b/maubot/management/frontend/src/style/index.sass
index b496d8b..9c1e193 100644
--- a/maubot/management/frontend/src/style/index.sass
+++ b/maubot/management/frontend/src/style/index.sass
@@ -27,3 +27,5 @@
@import pages/login
@import pages/dashboard
+@import pages/modal
+@import pages/log
diff --git a/maubot/management/frontend/src/style/pages/dashboard.sass b/maubot/management/frontend/src/style/pages/dashboard.sass
index 5568854..d04cbba 100644
--- a/maubot/management/frontend/src/style/pages/dashboard.sass
+++ b/maubot/management/frontend/src/style/pages/dashboard.sass
@@ -89,29 +89,16 @@
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: pre
-
- > span.level:before
- content: " ["
- > span.logger:before
- content: "@"
- > span.text:before
- content: "] "
-
div.buttons
+button-group
display: flex
margin: 1rem .5rem
width: calc(100% - 1rem)
+ button.open-log
+ +button
+ +main-color-button
+
div.error
+notification($error)
margin: 1rem .5rem
diff --git a/maubot/management/frontend/src/style/pages/log.sass b/maubot/management/frontend/src/style/pages/log.sass
new file mode 100644
index 0000000..8b75dfe
--- /dev/null
+++ b/maubot/management/frontend/src/style/pages/log.sass
@@ -0,0 +1,80 @@
+// 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 .
+div.log
+ height: 100%
+ width: 100%
+ overflow: auto
+
+ > div.lines
+ text-align: left
+ font-size: 12px
+ max-height: 100%
+ min-width: 100%
+ font-family: "Fira Code", monospace
+ display: table
+
+ > div.row
+ display: table-row
+ white-space: pre
+
+ &.debug
+ background-color: $background
+
+ &:nth-child(odd)
+ background-color: $background-dark
+
+ &.info
+ background-color: #AAFAFA
+
+ &:nth-child(odd)
+ background-color: #66FAFA
+
+ &.warning, &.warn
+ background-color: #FABB77
+
+ &:nth-child(odd)
+ background-color: #FAAA55
+
+ &.error
+ background-color: #FAAAAA
+
+ &:nth-child(odd)
+ background-color: #FA9999
+
+ &.fatal
+ background-color: #CC44CC
+
+ &:nth-child(odd)
+ background-color: #AA44AA
+
+ &.unfocused
+ opacity: .25
+
+ > span
+ padding: .125rem .25rem
+ display: table-cell
+
+ a
+ color: inherit
+ text-decoration: none
+
+ &:hover
+ text-decoration: underline
+
+ > span.text
+ > div.content > *
+ background-color: inherit !important
+ margin: 0 !important
diff --git a/maubot/management/frontend/src/style/pages/modal.sass b/maubot/management/frontend/src/style/pages/modal.sass
new file mode 100644
index 0000000..876e563
--- /dev/null
+++ b/maubot/management/frontend/src/style/pages/modal.sass
@@ -0,0 +1,71 @@
+// 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 .
+div.modal-wrapper-wrapper
+ z-index: 9001
+ position: fixed
+ top: 0
+ bottom: 0
+ left: 0
+ right: 0
+ background-color: rgba(0, 0, 0, 0.5)
+
+ --modal-margin: 2.5rem
+ --button-height: 0rem
+
+ @media screen and (max-width: 45rem)
+ --modal-margin: 1rem
+ --button-height: 2.5rem
+
+ @media screen and (max-width: 35rem)
+ --modal-margin: 0rem
+ --button-height: 3rem
+
+ button.close
+ +button
+
+ display: none
+
+ width: 100%
+ height: var(--button-height)
+ border-radius: .25rem .25rem 0 0
+
+ @media screen and (max-width: 45rem)
+ display: block
+ @media screen and (max-width: 35rem)
+ border-radius: 0
+
+ div.modal-wrapper
+ width: calc(100% - 2 * var(--modal-margin))
+ height: calc(100% - 2 * var(--modal-margin) - var(--button-height))
+ margin: var(--modal-margin)
+ border-radius: .25rem
+
+ @media screen and (max-width: 35rem)
+ border-radius: 0
+
+ div.modal
+ padding: 1rem
+ height: 100%
+ width: 100%
+ background-color: $background
+ box-sizing: border-box
+ border-radius: .25rem
+
+ @media screen and (max-width: 45rem)
+ border-radius: 0 0 .25rem .25rem
+ @media screen and (max-width: 35rem)
+ border-radius: 0
+ padding: .5rem
diff --git a/maubot/management/frontend/yarn.lock b/maubot/management/frontend/yarn.lock
index a0489ab..f72ec5e 100644
--- a/maubot/management/frontend/yarn.lock
+++ b/maubot/management/frontend/yarn.lock
@@ -1770,7 +1770,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -1829,6 +1829,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base16@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
+ integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
+
base64-js@^1.0.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
@@ -6303,11 +6308,21 @@ lodash.clonedeep@^4.3.2:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+lodash.curry@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
+ integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
+
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+lodash.flow@^3.3.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
+ integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
+
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -8366,6 +8381,11 @@ punycode@^1.2.4, punycode@^1.4.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+pure-color@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
+ integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
+
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -8476,6 +8496,16 @@ react-app-polyfill@^0.1.3:
raf "3.4.0"
whatwg-fetch "3.0.0"
+react-base16-styling@^0.5.1:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.5.3.tgz#3858f24e9c4dd8cbd3f702f3f74d581ca2917269"
+ integrity sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=
+ dependencies:
+ base16 "^1.0.0"
+ lodash.curry "^4.0.1"
+ lodash.flow "^3.3.0"
+ pure-color "^1.2.0"
+
react-dev-utils@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-6.0.5.tgz#6ef34d0a416dc1c97ac20025031ea1f0d819b21d"
@@ -8526,6 +8556,15 @@ react-input-autosize@^2.2.1:
dependencies:
prop-types "^15.5.8"
+react-json-tree@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.11.0.tgz#f5b17e83329a9c76ae38be5c04fda3a7fd684a35"
+ integrity sha1-9bF+gzKanHauOL5cBP2jp/1oSjU=
+ dependencies:
+ babel-runtime "^6.6.1"
+ prop-types "^15.5.8"
+ react-base16-styling "^0.5.1"
+
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"