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