-4

I'm running a bot and I'm trying to output the bot's logs to a simple webpage as a stream of HTML content (each log is sent as <p>{log}</p>). If I output the stream to route "/log_stream" and then go to that route, I can see the logs and they update and everything works as expected. However, I can't figure out how to incorporate that stream in another webpage. I tried embedding it using an <iframe> with the "/log_stream" route as the src but then the iframe wouldn't render at all. Any idea how to make this work?

NOTE: Please don't mark this as a duplicate of Display data streamed from a Flask view as it updates The answer there is either outdated or incorrect. Although you can't update the template directly after it's been rendered on the server, you definitely can embed dynamic content in that template using an iframe or img, like in this tutorial where he uses an image dynamically to create a video stream. Not being able to dynamically update the template itself is NOT the same thing as not being able to dynamically update the content IN that template.

app.py:

from flask import Flask, render_template
from threading import Thread

from bot import Bot
from logger import Logger
from config import config

application = Flask(__name__)
logger = Logger()

@application.route('/')
def index():
    return render_template('index.html.jinja', products=config['targets'], status='running')

@application.route('/log_stream')
def log_stream():
    return logger.printerStream()

if __name__ == '__main__':
    bot = Bot(logger)
    botThread = Thread(target=bot.run)
    botThread.start()
    application.run(host='0.0.0.0', debug=True)

logger.py (relevant parts):

def getLogHTML(self, logObj):
    if logObj['type'] == INFO:
        return "<p style=\"color:green\">"+logObj['text']+"</p>\n"
    if logObj['type'] == WARN:
        return "<p style=\"color:yellow\">"+logObj['text']+"</p>\n"
    if logObj['type'] == ERROR:
        return "<p style=\"color:red\">"+logObj['text']+"</p>\n"
    if logObj['type'] == EXCITE:
        return "<p style=\"color:blue\">"+logObj['text']+"</p>\n"
    return "<p style=\"color:white\">"+logObj['text']+"</p>\n"

def printerStream(self):
    self.isStreaming = True
    self.logs = self.getStoredLogs()
    self.logs.extend(self.secondaryLogs)
    self.storeLogs(self.secondaryLogs)
    self.secondaryLogs = deque()
    def generate():
        try:
            while True:
                if len(self.logsToStore) > 0:
                    self.storeLogs(self.logsToStore)
                    self.logsToStore = deque()
                nextLog = self.getNextLog()
                nextLogHTML = self.getLogHTML(nextLog)
                yield nextLogHTML
        except GeneratorExit:
            self.isStreaming = False
        self.isStreaming = False
    return Response(generate(), mimetype='text/html') # maybe text/html?

index.html.jinja:

<!doctype html>
<head>
<title>Title</title>
<style>
    table, th, td {
        border: 1px solid black;
        border-collapse: collapse;
    }
    body {
        color: green
    }
    .title {
        display: flex;
        flex-direction: row;
        justify-content:center
    }
    h1 {
        margin-bottom: 6px;
    }
    .row {
        display: flex;
    }
    .column {
        flex: 50%
    }
</style>
</head>
<body>
    <div class="title">
        <h1>Title</h1>
        <h2>Subtitle</h2>
    </div>
    <div class="row">
        <div class="column">
            </iframe src="{{ url_for('log_stream')}}">
        </div>
        <div class="column">
            <div class="row">
                <div class="column">
                    <p>Products: </p>
                </div>
                <div class="column">
                    {% for product in products %}
                        <p>{{product.name}}</p>
                    {% endfor %}
                </div>
            </div>
            <div class="row">
                <div class="column">
                    <p>Status: </p>
                </div>
                <div class="column">
                    <p>{{ status }}</p>
                </div>
            </div>
        </div>
    </div>
</body>
Marcellus
  • 441
  • 5
  • 16

1 Answers1

0

Actually, I solved this by adding styling to the <iframe>. Changing it to:

<iframe frameborder='0' noresize='noresize'
    style='position: absolute; background: transparent; width: 100%; height:100%;'
    src="{{ url_for('log_stream')}}"
    frameborder="0"></iframe>

accomplished pretty much what I wanted!

yivi
  • 42,438
  • 18
  • 116
  • 138
Marcellus
  • 441
  • 5
  • 16