feat: working admin interface
(need rework/styling)
This commit is contained in:
parent
32cd1080bf
commit
89d3ef103e
|
@ -30,13 +30,13 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||
var passwordInput = document.querySelector('#password-text');
|
||||
let websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||
let passwordInput = document.querySelector('#password-text');
|
||||
|
||||
var divLogin = document.querySelector("#login");
|
||||
var divDashboard = document.querySelector("#dashboard");
|
||||
var stateList = document.querySelector("#stateList");
|
||||
var saveButton = document.getElementById('save');
|
||||
let divLogin = document.querySelector("#login");
|
||||
let divDashboard = document.querySelector("#dashboard");
|
||||
let stateList = document.querySelector("#stateList");
|
||||
let saveButton = document.getElementById('save');
|
||||
|
||||
saveButton.onclick = function (event) {
|
||||
websocket.send(JSON.stringify({ 'admin': 'save' }));
|
||||
|
@ -44,11 +44,14 @@
|
|||
|
||||
function receiveStates(ev) {
|
||||
let msg = JSON.parse(ev.data);
|
||||
let states = msg.state
|
||||
for (var i = 0; i < states.length; i++) {
|
||||
var state = states[i];
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(document.createTextNode(state));
|
||||
let states = msg.states
|
||||
for (let i = 0; i < states.length; i++) {
|
||||
let state = states[i];
|
||||
let li = document.createElement('li');
|
||||
let button = document.createElement('button');
|
||||
button.onclick = () => websocket.send(JSON.stringify({ 'admin': "load:" + state }))
|
||||
button.appendChild(document.createTextNode(state));
|
||||
li.appendChild(button);
|
||||
stateList.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
@ -59,13 +62,12 @@
|
|||
divLogin.style.display = "none";
|
||||
divDashboard.style.display = "unset";
|
||||
websocket.removeEventListener('message', authSuccess);
|
||||
websocket.send(JSON.stringify({ "state": "get" }));
|
||||
websocket.addEventListener('message', receiveStates);
|
||||
receiveStates(ev);
|
||||
}
|
||||
};
|
||||
|
||||
function sendCreds() {
|
||||
var message = JSON.stringify({ "auth": passwordInput.value });
|
||||
let message = JSON.stringify({ "auth": passwordInput.value });
|
||||
websocket.send(message)
|
||||
websocket.addEventListener('message', authSuccess);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from subprocess import PIPE, Popen # nosec
|
||||
|
@ -9,6 +10,7 @@ import mgba.image
|
|||
import mgba.log
|
||||
import redis
|
||||
|
||||
import utils
|
||||
from settings import (
|
||||
EMULATOR_FPS,
|
||||
EMULATOR_HEIGHT,
|
||||
|
@ -27,7 +29,6 @@ from settings import (
|
|||
REDIS_PORT,
|
||||
RTMP_STREAM_URI,
|
||||
)
|
||||
from utils import States
|
||||
|
||||
core = mgba.core.load_path(EMULATOR_ROM_PATH)
|
||||
screen = mgba.image.Image(EMULATOR_WIDTH, EMULATOR_HEIGHT)
|
||||
|
@ -38,8 +39,6 @@ logging.basicConfig(level=logging.DEBUG)
|
|||
mgba.log.silence()
|
||||
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
|
||||
|
||||
states: States = States()
|
||||
|
||||
|
||||
def next_action():
|
||||
"""Select the next key from the redis database.
|
||||
|
@ -97,13 +96,12 @@ def state_manager(loop):
|
|||
ps.subscribe("admin")
|
||||
while True:
|
||||
for message in ps.listen():
|
||||
logging.debug(message)
|
||||
if message["type"] == "message":
|
||||
data = message["data"].decode("utf-8")
|
||||
if data == "save":
|
||||
asyncio.ensure_future(states.save(core), loop=loop)
|
||||
asyncio.ensure_future(utils.save(core), loop=loop)
|
||||
elif data.startswith("load:"):
|
||||
asyncio.ensure_future(states.load(core, data.removeprefix("load:")), loop=loop)
|
||||
asyncio.ensure_future(utils.load(core, data.removeprefix("load:")), loop=loop)
|
||||
|
||||
|
||||
async def emulator():
|
||||
|
@ -127,6 +125,13 @@ async def emulator():
|
|||
|
||||
|
||||
async def main(loop):
|
||||
|
||||
# setup states in redis
|
||||
files = os.listdir("states")
|
||||
states = list(filter(lambda x: x.endswith(".state"), files))
|
||||
for state in states:
|
||||
r.sadd("states", state.removesuffix(".state")) # voir si oneline possible
|
||||
|
||||
thread = threading.Thread(target=state_manager, args=(loop,))
|
||||
thread.start()
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Union
|
||||
|
||||
import redis
|
||||
import websockets
|
||||
|
@ -38,14 +39,20 @@ async def parse_message(user: User, message: dict[str, str]) -> None:
|
|||
if USERS.admin is None and data == PASSWORD_ADMIN:
|
||||
USERS.admin = user
|
||||
logging.debug(f"admin authenticated: {user}")
|
||||
await user.send('{"auth":"success"}')
|
||||
|
||||
response: dict[str, Union[str, list[str]]] = dict()
|
||||
response["auth"] = "success"
|
||||
states = r.smembers("states")
|
||||
stringlist = [x.decode("utf-8") for x in states]
|
||||
response["states"] = sorted(stringlist)
|
||||
await user.send(json.dumps(response))
|
||||
|
||||
if "admin" in message:
|
||||
if user == USERS.admin:
|
||||
data = message["admin"]
|
||||
if data == "save":
|
||||
r.publish("admin", "save")
|
||||
elif data == data.startswith("load:"):
|
||||
elif data.startswith("load:"):
|
||||
r.publish("admin", data)
|
||||
else:
|
||||
logging.error(f"unsupported admin action: {data}")
|
||||
|
@ -82,6 +89,8 @@ async def handler(websocket, path: str):
|
|||
await parse_message(user, message)
|
||||
finally:
|
||||
# Unregister user
|
||||
if user == USERS.admin:
|
||||
USERS.admin = None
|
||||
USERS.unregister(user)
|
||||
|
||||
|
||||
|
|
35
src/utils.py
35
src/utils.py
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
import os
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
@ -64,25 +63,19 @@ class Users(set):
|
|||
logging.debug(f"user unregistered: {self}")
|
||||
|
||||
|
||||
class States(set):
|
||||
"""Save and load states from files."""
|
||||
async def save(core):
|
||||
state = core.save_raw_state()
|
||||
current_time = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
with open(f"states/{current_time}.state", "wb") as state_file:
|
||||
for byte in state:
|
||||
state_file.write(byte.to_bytes(4, byteorder="big", signed=False))
|
||||
logging.debug(f"state saved : {current_time}.state")
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Construct a `States` object."""
|
||||
files = os.listdir("states")
|
||||
states = list(filter(lambda x: x.endswith(".state"), files))
|
||||
self.update(states)
|
||||
|
||||
async def save(self, core):
|
||||
state = core.save_raw_state()
|
||||
with open(f"states/{time.strftime('%Y-%m-%dT%H:%M:%S')}.state", "wb") as state_file:
|
||||
for byte in state:
|
||||
state_file.write(byte.to_bytes(4, byteorder="big", signed=False))
|
||||
self.add(state)
|
||||
|
||||
async def load(self, core, filename):
|
||||
state = ffi.new("unsigned char[397312]") # pulled 397312 from my ass
|
||||
with open(f"states/{filename}.state", "rb") as state_file:
|
||||
for i in range(len(state)):
|
||||
state[i] = int.from_bytes(state_file.read(4), byteorder="big", signed=False)
|
||||
core.load_raw_state(state)
|
||||
async def load(core, filename):
|
||||
state = ffi.new("unsigned char[397312]") # pulled 397312 from my ass
|
||||
with open(f"states/{filename}.state", "rb") as state_file:
|
||||
for i in range(len(state)):
|
||||
state[i] = int.from_bytes(state_file.read(4), byteorder="big", signed=False)
|
||||
core.load_raw_state(state)
|
||||
logging.debug(f"state loaded : {filename}")
|
||||
|
|
Loading…
Reference in a new issue