Add log viewer to mbc
This commit is contained in:
parent
ecc1843119
commit
6f06eec3cc
5 changed files with 141 additions and 17 deletions
|
@ -1 +1 @@
|
|||
from . import upload, build, login, init
|
||||
from . import upload, build, login, init, logs
|
||||
|
|
|
@ -27,7 +27,7 @@ import click
|
|||
from ...loader import PluginMeta
|
||||
from ..cliq.validators import PathValidator
|
||||
from ..base import app
|
||||
from ..config import config
|
||||
from ..config import get_default_server
|
||||
from .upload import upload_file
|
||||
|
||||
yaml = YAML()
|
||||
|
@ -98,11 +98,8 @@ def write_plugin(meta: PluginMeta, output: Union[str, IO]) -> None:
|
|||
|
||||
|
||||
def upload_plugin(output: Union[str, IO]) -> None:
|
||||
try:
|
||||
server = config["default_server"]
|
||||
token = config["servers"][server]
|
||||
except KeyError:
|
||||
print(Fore.RED + "Default server not configured." + Fore.RESET)
|
||||
server, token = get_default_server()
|
||||
if not token:
|
||||
return
|
||||
if isinstance(output, str):
|
||||
with open(output, "rb") as file:
|
||||
|
|
110
maubot/cli/commands/logs.py
Normal file
110
maubot/cli/commands/logs.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
# 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/>.
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
from colorama import Fore
|
||||
from aiohttp import WSMsgType, WSMessage, ClientSession
|
||||
from mautrix.client.api.types.util import Obj
|
||||
import click
|
||||
|
||||
from ..config import get_token, get_default_server
|
||||
from ..base import app
|
||||
|
||||
history_count: int = 10
|
||||
|
||||
|
||||
@app.command(help="View the logs of a server")
|
||||
@click.argument("server", required=False)
|
||||
@click.option("-t", "--tail", default=10, help="Maximum number of old log lines to display")
|
||||
def logs(server: str, tail: int) -> None:
|
||||
if not server:
|
||||
server, token = get_default_server()
|
||||
else:
|
||||
token = get_token(server)
|
||||
if not token:
|
||||
return
|
||||
global history_count
|
||||
history_count = tail
|
||||
loop = asyncio.get_event_loop()
|
||||
future = asyncio.ensure_future(view_logs(server, token), loop=loop)
|
||||
try:
|
||||
loop.run_until_complete(future)
|
||||
except KeyboardInterrupt:
|
||||
future.cancel()
|
||||
loop.run_until_complete(future)
|
||||
loop.close()
|
||||
|
||||
|
||||
def parsedate(entry: Obj) -> None:
|
||||
i = entry.time.index("+")
|
||||
i = entry.time.index(":", i)
|
||||
entry.time = entry.time[:i] + entry.time[i + 1:]
|
||||
entry.time = datetime.strptime(entry.time, "%Y-%m-%dT%H:%M:%S.%f%z")
|
||||
|
||||
|
||||
levelcolors = {
|
||||
"DEBUG": "",
|
||||
"INFO": Fore.CYAN,
|
||||
"WARNING": Fore.YELLOW,
|
||||
"ERROR": Fore.RED,
|
||||
"FATAL": Fore.MAGENTA,
|
||||
}
|
||||
|
||||
|
||||
def print_entry(entry: dict) -> None:
|
||||
entry = Obj(**entry)
|
||||
parsedate(entry)
|
||||
print("{levelcolor}[{date}] [{level}@{logger}] {message}{resetcolor}"
|
||||
.format(date=entry.time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
level=entry.levelname,
|
||||
levelcolor=levelcolors.get(entry.levelname, ""),
|
||||
resetcolor=Fore.RESET,
|
||||
logger=entry.name,
|
||||
message=entry.msg))
|
||||
|
||||
|
||||
def handle_msg(data: dict) -> bool:
|
||||
if "auth_success" in data:
|
||||
if data["auth_success"]:
|
||||
print(Fore.GREEN + "Connected to log websocket" + Fore.RESET)
|
||||
else:
|
||||
print(Fore.RED + "Failed to authenticate to log websocket" + Fore.RESET)
|
||||
return False
|
||||
elif "history" in data:
|
||||
for entry in data["history"][-history_count:]:
|
||||
print_entry(entry)
|
||||
else:
|
||||
print_entry(data)
|
||||
return True
|
||||
|
||||
|
||||
async def view_logs(server: str, token: str) -> None:
|
||||
async with ClientSession() as session:
|
||||
async with session.ws_connect(f"{server}/_matrix/maubot/v1/logs") as ws:
|
||||
await ws.send_str(token)
|
||||
try:
|
||||
msg: WSMessage
|
||||
async for msg in ws:
|
||||
if msg.type == WSMsgType.TEXT:
|
||||
if not handle_msg(msg.json()):
|
||||
break
|
||||
elif msg.type == WSMsgType.ERROR:
|
||||
print(Fore.YELLOW + "Connection error: " + msg.data + Fore.RESET)
|
||||
elif msg.type == WSMsgType.CLOSE:
|
||||
print(Fore.YELLOW + "Server closed connection" + Fore.RESET)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
|
@ -22,7 +22,7 @@ from colorama import Fore
|
|||
import click
|
||||
|
||||
from ..base import app
|
||||
from ..config import config
|
||||
from ..config import get_default_server, get_token
|
||||
|
||||
|
||||
class UploadError(Exception):
|
||||
|
@ -34,15 +34,10 @@ class UploadError(Exception):
|
|||
@click.option("-s", "--server", help="The maubot instance to upload the plugin to")
|
||||
def upload(path: str, server: str) -> None:
|
||||
if not server:
|
||||
try:
|
||||
server = config["default_server"]
|
||||
except KeyError:
|
||||
print(Fore.RED + "Default server not configured" + Fore.RESET)
|
||||
return
|
||||
try:
|
||||
token = config["servers"][server]
|
||||
except KeyError:
|
||||
print(Fore.RED + "Server not found" + Fore.RESET)
|
||||
server, token = get_default_server()
|
||||
else:
|
||||
token = get_token(server)
|
||||
if not token:
|
||||
return
|
||||
with open(path, "rb") as file:
|
||||
upload_file(file, server, token)
|
||||
|
|
|
@ -13,9 +13,12 @@
|
|||
#
|
||||
# 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/>.
|
||||
from typing import Tuple, Optional
|
||||
import json
|
||||
import os
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
config = {
|
||||
"servers": {},
|
||||
"default_server": None,
|
||||
|
@ -23,6 +26,25 @@ config = {
|
|||
configdir = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.environ.get("HOME"), ".config"))
|
||||
|
||||
|
||||
def get_default_server() -> Tuple[Optional[str], Optional[str]]:
|
||||
try:
|
||||
server: str = config["default_server"]
|
||||
except KeyError:
|
||||
server = None
|
||||
if server is None:
|
||||
print(f"{Fore.RED}Default server not configured.{Fore.RESET}")
|
||||
return None, None
|
||||
return server, get_token(server)
|
||||
|
||||
|
||||
def get_token(server: str) -> Optional[str]:
|
||||
try:
|
||||
return config["servers"][server]
|
||||
except KeyError:
|
||||
print(f"{Fore.RED}No access token saved for {server}.{Fore.RESET}")
|
||||
return None
|
||||
|
||||
|
||||
def save_config() -> None:
|
||||
with open(f"{configdir}/maubot-cli.json", "w") as file:
|
||||
json.dump(config, file)
|
||||
|
|
Loading…
Reference in a new issue