its a demo

This commit is contained in:
Morgan 'ARR\!' Allen 2023-10-18 13:51:32 -07:00
commit 13b87b97a3
4 changed files with 333 additions and 0 deletions

71
app.py Normal file
View File

@ -0,0 +1,71 @@
import time
import json
from random import choice, random
from flask import Flask, render_template
from flask_sock import Sock
app = Flask(__name__)
sock = Sock(app)
app.debug = True
@app.after_request
def add_header(r):
"""
Add headers to both force latest IE rendering engine or Chrome Frame,
and also to cache the rendered page for 10 minutes.
"""
r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
r.headers["Pragma"] = "no-cache"
r.headers["Expires"] = "0"
r.headers['Cache-Control'] = 'public, max-age=0'
return r
def strftime():
return time.strftime('%H:%m:%S')
@app.route('/')
def index():
return render_template('index.html')
@sock.route('/time')
def route_time(sock):
while True:
sock.send(strftime())
time.sleep(1)
@sock.route('/logs')
def route_logs(sock):
id = 0;
sock.send(json.dumps({
'id': 'Id',
'time': 'Time',
'value': 'Value',
'message': 'Message'
}))
with open('/usr/share/wordlists/wordlist') as wl:
lines = wl.readlines()
while True:
time.sleep(random() * 3)
message = '{} {} {}'.format(choice(lines), choice(lines), choice(lines))
sock.send(json.dumps({
'id': id,
'time': str(strftime()),
'value': int(random() * 100),
'message': message
}))
id = id + 1
@sock.route('/ticker')
def route_ticker(sock):
tick = 0;
while True:
sock.send(tick);
tick = tick + 1
time.sleep(0.5)

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
Flask==3.0.0
flask-sock==0.7.0

219
static/index.js Normal file
View File

@ -0,0 +1,219 @@
class WSElement extends HTMLElement {
static #sockets = {};
#delay = 1000;
constructor() {
super();
}
connectedCallback() {
// track `src` and `autoreconnect` attributes for later use
// `autoreconnect` defaults to `true`, unless explicitly set to `false`
this.src = this.getAttribute('src');
this.autoreconnect = !this.hasAttribute('autoreconnect') || this.getAttribute('autoreconnect').toLowerCase() !== 'false';
this.#connect_socket(false);
}
#connect_socket(recreate) {
var ws;
// `recreate` is used to handle reconnects, removeing the old websocket object
// and recreating a new one
if(recreate && WSElement.#sockets[this.src]) {
delete WSElement.#sockets[this.src];
}
// a single WebSocket can be reused for multiple elements
// check to see if one exists for this `src` else create
// a new one.
if(!(this.src in WSElement.#sockets)) {
ws = WSElement.#newSocket(this.src);
WSElement.#sockets[this.src] = ws;
} else {
ws = WSElement.#sockets[this.src];
}
// setup event handlers for this WebSocket/Elemnent pair
ws.addEventListener('error', this.#on_ws_error.bind(this));
ws.addEventListener('message', this.#on_ws_message.bind(this));
ws.addEventListener('open', this.#on_ws_open.bind(this));
ws.addEventListener('close', this.#on_ws_close.bind(this));
}
#on_ws_open(sock) {
// set connected attribute for use in CSS
// ```
// ws-element[connected] {
// color: green;
// }
// ws-element:not(connected) {
// color: red;
// }
// ```
this.setAttribute('connected', '');
// call this.open if defined by subclass
if('open' in this) {
this.open(sock);
}
}
#on_ws_message(sock) {
// call this.message, which is always defined
this.message(sock.data);
}
#on_ws_close(sock) {
this.removeAttribute('connected');
// clean up event handlers for this element
sock.target.removeEventListener('error', this.#on_ws_error.bind(this));
sock.target.removeEventListener('message', this.#on_ws_message.bind(this));
sock.target.removeEventListener('open', this.#on_ws_open.bind(this));
sock.target.removeEventListener('close', this.#on_ws_close.bind(this));
// call this.close if subclass has defined it
if('close' in this) {
this.close(sock);
}
// handle autoreconnect and delay throttling
if(this.autoreconnect) {
setTimeout(this.#connect_socket.call(this, true), this.#delay);
if(this.#delay < 10000) {
this.#delay += 1000;
}
}
}
#on_ws_error(sock) {
// call this.error if subclass has defined it
if('error' in this) {
this.error(sock);
}
}
// default behavior on this.message is just update innerText
message(data) {
this.innerText = data;
}
// static method for creating new sockets, which are tracked
// on the static propert WSElement.#sockets
static #newSocket(src) {
var s = window.location.protocol.slice(-2) === 'p:' ? '' : 's';
var host = window.location.host;
var path = src[0] === '/' ? src.slice(1) : src;
var wsurl = `ws${s}://${host}/${path}`;
return new WebSocket(wsurl);
}
}
// Example subclass to display a basic 'Time: 00:00:00'
class WSTime extends WSElement {
connectedCallback() {
super.connectedCallback();
// insert subelements for the label (Time:) and the value;
this.insertAdjacentHTML('beforeend', '<label class="time-label">Time: </label><span class="time-value"></span>');
// track the value element for updating later
this.value = this.querySelector('span.time-value');
}
// override this.message to update the innerText of the value span element
message(data) {
this.value.innerText = data;
}
}
// Example subclass to parse arbitrary json and build a table
// with dynamically controlled limit
class WSTable extends WSElement {
// observe limit to dynamically update
static observedAttributes = [ 'limit' ];
connectedCallback() {
super.connectedCallback();
// ignoreNext is to handle duplicate headers being sent on reconnect
this.ignoreNext = false;
// insert and track table element
this.insertAdjacentHTML('beforeend', '<table class="table"></table>');
this.table = this.querySelector('table');
// track limit attribute, defaulting to no limit
if(this.hasAttribute('limit')) {
this.limit = parseInt(this.getAttribute('limit')) || -1;
}
}
attributeChangedCallback(name, oldValue, newValue) {
// update the tracked limit, defaulting to none if no value provided
if(oldValue === null) {
this.limit = parseInt(newValue) || -1;
}
}
message(data) {
// ignore one message, because the demo server is a bit dumb
if(this.ignoreNext) {
this.ignoreNext = false;
return;
}
data = JSON.parse(data);
// with autoheader set the first message will create a header row instead of body
if(this.hasAttribute('autoheader') && !('header' in this)) {
// createElement works just as well as insertAdjacentHTML for single, to be tracked elements
this.thead = document.createElement('thead');
this.header = document.createElement('tr');
this.thead.appendChild(this.header);
this.table.appendChild(this.thead);
// add a `th` for each key/value pair in the json data
for(let k in data) {
this.header.insertAdjacentHTML('beforeend', `<th scope="col" class="${k}">${data[k]}</th>`);
}
// work is done, this block will be ignored in future messages
return;
}
// create the body if it doesn't yet exists
if(!('tbody' in this)) {
this.tbody = document.createElement('tbody');
this.table.appendChild(this.tbody);
}
// create TD for each key/value pair in message data
var inner = '';
for(let k in data) {
inner += `<td class="${k}">${data[k]}</td>`;
}
// instead the above TDs into a new TR
this.tbody.insertAdjacentHTML('beforeend', `<tr>${inner}</tr>`);
// check limits are enforced
if(this.limit > -1 && this.tbody.childElementCount > this.limit) {
while(this.tbody.childElementCount > this.limit) {
this.tbody.removeChild(this.tbody.childNodes[0]);
}
}
}
// ignore the next message if we're disconnected,
// demo server always sends headers on connect
close(sock) {
this.ignoreNext = true;
}
}
// define custom elements
customElements.define('ws-element', WSElement);
customElements.define('ws-time', WSTime);
customElements.define('ws-table', WSTable);

41
templates/index.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<style>
ws-time[connected] {
color: green;
}
ws-time:not(connected) {
color: red;
}
ws-table tr {
animation: fadein 1s;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
<script type="module" src="{{ url_for('static', filename='index.js') }}"></script>
</head>
<body>
<div class="container-xxl">
<main class="bd-main">
<ws-time src="/time"></ws-time>
<div>
<ws-element autoreconnect="false" src="/ticker"></ws-element>
</div>
<ws-table limit="10" autoheader src="/logs"></ws-table>
</main>
</div>
</body>
</html>