Browse Source

Merge pull request 'Web ui merge' (#2) from ideas into master

Reviewed-on: #2
master
Sergei Alexeyev 3 years ago
parent
commit
80733f1b2d
  1. 1
      bot.py
  2. BIN
      cogs/web/assets/favicon.ico
  3. BIN
      cogs/web/assets/fweddit.png
  4. 7
      cogs/web/assets/style.css
  5. 41
      cogs/web/templates/base.html
  6. 47
      cogs/web/templates/index.html
  7. 29
      cogs/web/templates/killstream.html
  8. 12
      cogs/web/templates/login.html
  9. 14
      cogs/web/templates/logs.html
  10. 76
      cogs/webserver.py
  11. 5
      config.py.example
  12. 4
      requirements.txt

1
bot.py

@ -42,6 +42,7 @@ initial_cogs = (
"cogs.killstream",
"cogs.units",
"cogs.esiprice",
"cogs.webserver"
)

BIN
cogs/web/assets/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
cogs/web/assets/fweddit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

7
cogs/web/assets/style.css

File diff suppressed because one or more lines are too long

41
cogs/web/templates/base.html

@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Rooster Web UI</title>
<link rel="shortcut icon" href="/assets/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<a class="navbar-brand" href="{{ url_for('index') }}">
<img src="/assets/fweddit.png" width="30" height="30" class="d-inline-block align-top" alt="">
Rooster Web UI
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logs') }}">Logs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('killstream') }}">Killstream</a>
</li>
{% endif %}
</ul>
{% if current_user.is_authenticated %}
<a class="my-2 my-lg-0 btn btn-outline-danger" href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>

47
cogs/web/templates/index.html

@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block content %}
<p>Rooster is currently in <span class="badge">{{ guilds|length }}</span> guilds and is serving
<span class="badge">{{ users|length }}</span> users.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>
<img src="{{ user.avatar_url }}" width="32" height="32" alt="Avatar"> {{ user.name }}
{% if user.bot %}<span class="badge badge-info">BOT</span>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
<th scope="col">Members</th>
<th scope="col">Channels</th>
</tr>
</thead>
<tbody>
{% for guild in guilds %}
<tr>
<td>{{ guild.id }}</td>
<td><img src="{{ guild.icon_url }}" width="32" height="32" alt="Icon"> {{ guild.name }}</td>
<td>{{ guild.members|length }}</td>
<td>{{ guild.channels|length }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

29
cogs/web/templates/killstream.html

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<p>We have <span class="badge">{{ killwatches|length }}</span> killwatches.
<table class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">Channel</th>
<th scope="col">Entity Name</th>
<th scope="col">Entity Id</th>
<th scope="col">Value</th>
<th scope="col">Scope</th>
</tr>
</thead>
<tbody>
{% for killwatch in killwatches %}
<tr>
<td>{{ killwatches[killwatch].channel }}</td>
<td>{{ killwatches[killwatch].name }}</td>
<td>{{ killwatches[killwatch].id }}</td>
<td>{{ killwatches[killwatch].value }}</td>
<td>{{ killwatches[killwatch].scope }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

12
cogs/web/templates/login.html

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
<p>You need to login to access this page.</p>
<form class="form" action="{{ url_for('login') }}" method="post">
<div class="form-group">
<label for="access_key">Access key</label>
<input type="password" class="form-control" id="access_key" name="access_key" required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}

14
cogs/web/templates/logs.html

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<table class="table table-bordered table-striped">
<tbody>
{% for log in logs %}
<tr>
<td>{{ log }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

76
cogs/webserver.py

@ -0,0 +1,76 @@
import json
import logging
import config
from discord.ext import commands, tasks
from quart import Quart, render_template, ResponseReturnValue, redirect, url_for, request
from quart.exceptions import Unauthorized
from quart_auth import AuthManager, AuthUser, login_required, login_user, logout_user
log = logging.getLogger(__name__)
app = Quart(__name__, static_url_path='/assets/', static_folder='web/assets', template_folder='web/templates')
app.secret_key = config.web_secret_key
AuthManager(app)
class Webserver(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.web_server.start()
@app.route('/')
@login_required
async def index():
return await render_template('index.html', guilds=self.bot.guilds, users=self.bot.users)
@app.route('/logs')
@login_required
async def logs():
with open('Rooster.log') as f:
content = f.readlines()
return await render_template('logs.html', logs=reversed([x.strip() for x in content]))
@app.route('/killstream/')
@login_required
async def killstream():
killwatches = await self.bot.redis.execute("get", "killwatch_criteria")
return await render_template('killstream.html', killwatches=json.loads(killwatches.decode("utf-8")))
@app.route('/send-message/<channel_id>')
@login_required
async def test_message(channel_id):
channel = self.bot.get_channel(int(channel_id))
await channel.send('web ui test')
return 'Done!'
@app.errorhandler(Unauthorized)
async def redirect_to_login(*_: Exception) -> ResponseReturnValue:
return redirect(url_for("login"))
@app.route("/login", methods=['GET', 'POST'])
async def login():
if request.method == 'POST':
access_key = (await request.form)["access_key"]
if access_key == config.web_access_key:
login_user(AuthUser(access_key))
return redirect(url_for('index'))
else:
return redirect(url_for('login'))
else:
return await render_template('login.html')
@app.route("/logout")
async def logout():
logout_user()
return redirect(url_for('index'))
@tasks.loop()
async def web_server(self):
await app.run_task(host=config.web_host, port=config.web_port, use_reloader=False)
@web_server.before_loop
async def web_server_before_loop(self):
await self.bot.wait_until_ready()
def setup(bot):
bot.add_cog(Webserver(bot))

5
config.py.example

@ -11,3 +11,8 @@ secret_key = ''
redis_host = 'localhost'
redis_port = 6379
web_host = 'localhost'
web_port = 5000
web_secret_key = 'replaceme'
web_access_key = 'replaceme'

4
requirements.txt

@ -17,4 +17,6 @@ logbook
sqlalchemy
pyyaml
pint
websockets
websockets
quart
quart-auth
Loading…
Cancel
Save