reformatted file structure and reformatted for phones, as well as fixed web sockets for remote devices

This commit is contained in:
UpstreamData
2022-03-08 11:39:10 -07:00
parent 7ba8044564
commit 43834203a8
20 changed files with 430 additions and 324 deletions

View File

@@ -1,252 +1,24 @@
import asyncio
import datetime
import ipaddress
import os import os
import uvicorn import uvicorn
import websockets.exceptions
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi import WebSocket, WebSocketDisconnect
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from network import MinerNetwork from tools.web_monitor.dashboard import router as dashboard_router
from tools.web_monitor.miner_factory import miner_factory from tools.web_monitor.miner import router as miner_router
from tools.web_monitor.web_settings import get_current_settings, update_settings from tools.web_monitor.scan import router as scan_router
from tools.web_monitor.settings import router as settings_router
app = FastAPI() app = FastAPI()
app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static") app.mount("/static", StaticFiles(
templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "templates")) directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
@app.get("/")
def index(request: Request):
return RedirectResponse(request.url_for('dashboard'))
@app.get("/dashboard")
def dashboard(request: Request):
return templates.TemplateResponse("index.html", {
"request": request,
"cur_miners": get_current_miner_list()
})
@app.websocket("/dashboard/ws")
async def dashboard_websocket(websocket: WebSocket):
await websocket.accept()
graph_sleep_time = get_current_settings()["graph_data_sleep_time"]
try:
while True:
miners = get_current_miner_list()
all_miner_data = []
data_gen = asyncio.as_completed(
[get_miner_data_dashboard(miner_ip) for miner_ip in miners])
for all_data in data_gen:
data_point = await all_data
all_miner_data.append(data_point)
all_miner_data.sort(key=lambda x: x["ip"])
await websocket.send_json(
{"datetime": datetime.datetime.now().isoformat(),
"miners": all_miner_data})
await asyncio.sleep(graph_sleep_time)
except WebSocketDisconnect:
print("Websocket disconnected.")
pass
except websockets.exceptions.ConnectionClosedOK:
pass
async def get_miner_data_dashboard(miner_ip):
try:
settings = get_current_settings()
miner_identify_timeout = settings["miner_identify_timeout"]
miner_data_timeout = settings["miner_data_timeout"]
miner_ip = await asyncio.wait_for(miner_factory.get_miner(miner_ip), miner_identify_timeout)
miner_summary = await asyncio.wait_for(miner_ip.api.summary(), miner_data_timeout)
if miner_summary:
if 'MHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(miner_summary['SUMMARY'][0]['MHS av'] / 1000000,
2), ".2f")
elif 'GHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(miner_summary['SUMMARY'][0]['GHS av'] / 1000, 2),
".2f")
else:
hashrate = 0
else:
hashrate = 0
return {"ip": str(miner_ip.ip), "hashrate": hashrate}
except asyncio.exceptions.TimeoutError:
return {"ip": miner_ip, "error": "The miner_ip is not responding."}
except KeyError:
return {"ip": miner_ip,
"error": "The miner_ip returned unusable/unsupported data."}
@app.get("/scan")
def scan(request: Request):
return templates.TemplateResponse("scan.html", {
"request": request,
"cur_miners": get_current_miner_list()
})
@app.get("/miner")
def miner(_request: Request, _miner_ip):
return get_miner
@app.websocket("/miner/{miner_ip}/ws")
async def miner_websocket(websocket: WebSocket, miner_ip):
await websocket.accept()
settings = get_current_settings()
miner_identify_timeout = settings["miner_identify_timeout"]
miner_data_timeout = settings["miner_data_timeout"]
try:
while True:
try:
cur_miner = await asyncio.wait_for(
miner_factory.get_miner(str(miner_ip)), miner_identify_timeout)
data = await asyncio.wait_for(
cur_miner.api.multicommand("summary", "fans", "stats"), miner_data_timeout)
miner_model = await cur_miner.get_model()
miner_summary = None
miner_fans = None
if "summary" in data.keys():
miner_summary = data["summary"][0]
elif "SUMMARY" in data.keys():
miner_summary = data
miner_fans = {"FANS": []}
for item in ["Fan Speed In", "Fan Speed Out"]:
if item in miner_summary["SUMMARY"][0].keys():
miner_fans["FANS"].append(
{"RPM": miner_summary["SUMMARY"][0][item]})
if "fans" in data.keys():
miner_fans = data["fans"][0]
if "stats" in data.keys():
miner_stats = data["stats"][0]
miner_fans = {"FANS": []}
for item in ["fan1", "fan2", "fan3", "fan4"]:
if item in miner_stats["STATS"][1].keys():
miner_fans["FANS"].append(
{"RPM": miner_stats["STATS"][1][item]})
if miner_summary:
if 'MHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(
miner_summary['SUMMARY'][0]['MHS av'] / 1000000,
2), ".2f")
elif 'GHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(miner_summary['SUMMARY'][0]['GHS av'] / 1000,
2),
".2f")
else:
hashrate = 0
else:
hashrate = 0
fan_speeds = []
if miner_fans:
for fan in miner_fans["FANS"]:
fan_speeds.append(fan["RPM"])
while len(fan_speeds) < 5:
fan_speeds.append(0)
data = {"hashrate": hashrate,
"fans": fan_speeds,
"datetime": datetime.datetime.now().isoformat(),
"model": miner_model}
await websocket.send_json(data)
await asyncio.sleep(settings["graph_sleep_time"])
except asyncio.exceptions.TimeoutError:
data = {"error": "The miner is not responding."}
await websocket.send_json(data)
await asyncio.sleep(.5)
except KeyError as e:
print(e)
data = {
"error": "The miner returned unusable/unsupported data."}
await websocket.send_json(data)
await asyncio.sleep(.5)
except WebSocketDisconnect:
print("Websocket disconnected.")
except websockets.exceptions.ConnectionClosedOK:
pass
@app.get("/miner/{miner_ip}")
def get_miner(request: Request, miner_ip):
return templates.TemplateResponse("miner.html", {
"request": request,
"cur_miners": get_current_miner_list(),
"miner": miner_ip
})
@app.get("/miner_ip/{miner_ip}/remove")
def get_miner(request: Request, miner_ip):
miners = get_current_miner_list()
miners.remove(miner_ip)
with open("miner_list.txt", "w") as file:
for miner_ip in miners:
file.write(miner_ip + "\n")
return RedirectResponse(request.url_for('dashboard'))
def get_current_miner_list():
cur_miners = []
if os.path.exists(os.path.join(os.getcwd(), "miner_list.txt")):
with open(os.path.join(os.getcwd(), "miner_list.txt")) as file:
for line in file.readlines():
cur_miners.append(line.strip())
cur_miners = sorted(cur_miners, key=lambda x: ipaddress.ip_address(x))
return cur_miners
@app.route("/settings", methods=["GET", "POST"])
async def settings(request: Request):
return templates.TemplateResponse("settings.html", {
"request": request,
"cur_miners": get_current_miner_list(),
"settings": get_current_settings()
})
@app.post("/settings/update")
async def update_settings_page(request: Request):
data = await request.form()
graph_data_sleep_time = data.get('graph_data_sleep_time')
miner_data_timeout = data.get('miner_data_timeout')
miner_identify_timeout = data.get('miner_identify_timeout')
new_settings = {
"graph_data_sleep_time": int(graph_data_sleep_time),
"miner_data_timeout": int(miner_data_timeout),
"miner_identify_timeout": int(miner_identify_timeout),
}
update_settings(new_settings)
return RedirectResponse(request.url_for("settings"))
app.include_router(dashboard_router, tags=["dashboard"])
app.include_router(miner_router, tags=["miner"], prefix="/miner")
app.include_router(scan_router, tags=["scan"], prefix="/scan")
app.include_router(settings_router, tags=["settings"], prefix="/settings")
@app.get("/remove_all_miners") @app.get("/remove_all_miners")
@@ -256,70 +28,5 @@ async def remove_all_miners(request: Request):
return RedirectResponse(request.url_for("settings")) return RedirectResponse(request.url_for("settings"))
@app.post("/scan/add_miners")
async def add_miners_scan(request: Request):
miners = await request.json()
with open("miner_list.txt", "a+") as file:
for miner_ip in miners["miners"]:
file.write(miner_ip + "\n")
return scan
@app.websocket("/scan/ws")
async def websocket_scan(websocket: WebSocket):
await websocket.accept()
cur_task = None
try:
while True:
ws_data = await websocket.receive_text()
if "-Cancel-" in ws_data:
if cur_task:
cur_task.cancel()
try:
await cur_task
except asyncio.CancelledError:
cur_task = None
await websocket.send_text("Cancelled")
else:
cur_task = asyncio.create_task(
do_websocket_scan(websocket, ws_data))
if cur_task and cur_task.done():
cur_task = None
except WebSocketDisconnect:
print("Websocket disconnected.")
except websockets.exceptions.ConnectionClosedOK:
pass
async def do_websocket_scan(websocket: WebSocket, network_ip: str):
cur_miners = get_current_miner_list()
try:
if "/" in network_ip:
network_ip, network_subnet = network_ip.split("/")
network = MinerNetwork(network_ip, mask=network_subnet)
else:
network = MinerNetwork(network_ip)
miner_generator = network.scan_network_generator()
miners = []
async for miner_ip in miner_generator:
if miner_ip and str(miner_ip) not in cur_miners:
miners.append(miner_ip)
get_miner_genenerator = miner_factory.get_miner_generator(miners)
all_miners = []
async for found_miner in get_miner_genenerator:
all_miners.append(
{"ip": found_miner.ip, "model": await found_miner.get_model()})
all_miners.sort(key=lambda x: x["ip"])
send_miners = []
for miner_ip in all_miners:
send_miners.append(
{"ip": str(miner_ip["ip"]), "model": miner_ip["model"]})
await websocket.send_json(send_miners)
await websocket.send_text("Done")
except asyncio.CancelledError:
raise
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=80) uvicorn.run("app:app", host="0.0.0.0", port=80)

View File

@@ -0,0 +1,24 @@
from fastapi import Request, APIRouter
from fastapi.responses import RedirectResponse
from tools.web_monitor.template import templates
from tools.web_monitor.func import get_current_miner_list
from .ws import router as ws_router
router = APIRouter()
router.include_router(ws_router)
@router.get("/")
def index(request: Request):
return RedirectResponse(request.url_for('dashboard'))
@router.get("/dashboard")
def dashboard(request: Request):
print()
return templates.TemplateResponse("index.html", {
"request": request,
"cur_miners": get_current_miner_list()
})

View File

@@ -0,0 +1,43 @@
import asyncio
from tools.web_monitor.miner_factory import miner_factory
from tools.web_monitor.settings.func import get_current_settings
async def get_miner_data_dashboard(miner_ip):
try:
settings = get_current_settings()
miner_identify_timeout = settings["miner_identify_timeout"]
miner_data_timeout = settings["miner_data_timeout"]
miner_ip = await asyncio.wait_for(
miner_factory.get_miner(miner_ip),
miner_identify_timeout
)
miner_summary = await asyncio.wait_for(
miner_ip.api.summary(),
miner_data_timeout
)
if miner_summary:
if 'MHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(miner_summary['SUMMARY'][0]['MHS av'] / 1000000,
2), ".2f")
elif 'GHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = format(
round(miner_summary['SUMMARY'][0]['GHS av'] / 1000, 2),
".2f")
else:
hashrate = 0
else:
hashrate = 0
return {"ip": str(miner_ip.ip), "hashrate": hashrate}
except asyncio.exceptions.TimeoutError:
return {"ip": miner_ip, "error": "The miner is not responding."}
except KeyError:
return {"ip": miner_ip,
"error": "The miner returned unusable/unsupported data."}

View File

@@ -0,0 +1,37 @@
import asyncio
import datetime
import websockets.exceptions
from fastapi import WebSocket, WebSocketDisconnect, APIRouter
from tools.web_monitor.func import get_current_miner_list
from tools.web_monitor.settings.func import get_current_settings
from tools.web_monitor.dashboard.func import get_miner_data_dashboard
router = APIRouter()
@router.websocket("/dashboard/ws")
async def dashboard_websocket(websocket: WebSocket):
await websocket.accept()
graph_sleep_time = get_current_settings()["graph_data_sleep_time"]
try:
while True:
miners = get_current_miner_list()
all_miner_data = []
data_gen = asyncio.as_completed(
[get_miner_data_dashboard(miner_ip) for miner_ip in miners])
for all_data in data_gen:
data_point = await all_data
all_miner_data.append(data_point)
all_miner_data.sort(key=lambda x: x["ip"])
await websocket.send_json(
{"datetime": datetime.datetime.now().isoformat(),
"miners": all_miner_data})
await asyncio.sleep(graph_sleep_time)
except WebSocketDisconnect:
print("Websocket disconnected.")
pass
except websockets.exceptions.ConnectionClosedOK:
pass

View File

@@ -0,0 +1,12 @@
import os
import ipaddress
def get_current_miner_list():
cur_miners = []
if os.path.exists(os.path.join(os.getcwd(), "miner_list.txt")):
with open(os.path.join(os.getcwd(), "miner_list.txt")) as file:
for line in file.readlines():
cur_miners.append(line.strip())
cur_miners = sorted(cur_miners, key=lambda x: ipaddress.ip_address(x))
return cur_miners

View File

@@ -0,0 +1,23 @@
from fastapi import Request, APIRouter
from tools.web_monitor.template import templates
from tools.web_monitor.func import get_current_miner_list
from .ws import router as ws_router
router = APIRouter()
router.include_router(ws_router)
@router.get("/")
def miner(_request: Request, _miner_ip):
return get_miner
@router.get("/{miner_ip}")
def get_miner(request: Request, miner_ip):
return templates.TemplateResponse("miner.html", {
"request": request,
"cur_miners": get_current_miner_list(),
"miner": miner_ip
})

View File

@@ -0,0 +1,16 @@
from fastapi import Request
from fastapi.responses import RedirectResponse
from tools.web_monitor.app import app
from tools.web_monitor.func import get_current_miner_list
@app.get("/{miner_ip}/remove")
def get_miner(request: Request, miner_ip):
miners = get_current_miner_list()
miners.remove(miner_ip)
with open("miner_list.txt", "w") as file:
for miner_ip in miners:
file.write(miner_ip + "\n")
return RedirectResponse(request.url_for('dashboard'))

View File

@@ -0,0 +1,103 @@
import asyncio
import datetime
import websockets.exceptions
from fastapi import WebSocket, WebSocketDisconnect, APIRouter
from tools.web_monitor.miner_factory import miner_factory
from tools.web_monitor.settings.func import get_current_settings
router = APIRouter()
@router.websocket("/{miner_ip}/ws")
async def miner_websocket(websocket: WebSocket, miner_ip):
await websocket.accept()
settings = get_current_settings()
miner_identify_timeout = settings["miner_identify_timeout"]
miner_data_timeout = settings["miner_data_timeout"]
try:
while True:
try:
cur_miner = await asyncio.wait_for(
miner_factory.get_miner(str(miner_ip)),
miner_identify_timeout
)
data = await asyncio.wait_for(
cur_miner.api.multicommand("summary", "fans", "stats"),
miner_data_timeout
)
print(data)
miner_model = await cur_miner.get_model()
miner_summary = None
miner_fans = None
if "summary" in data.keys():
miner_summary = data["summary"][0]
elif "SUMMARY" in data.keys():
miner_summary = data
miner_fans = {"FANS": []}
for item in ["Fan Speed In", "Fan Speed Out"]:
if item in miner_summary["SUMMARY"][0].keys():
miner_fans["FANS"].append(
{"RPM": miner_summary["SUMMARY"][0][item]})
if "fans" in data.keys():
miner_fans = data["fans"][0]
if "stats" in data.keys() and not miner_fans:
miner_stats = data["stats"][0]
miner_fans = {"FANS": []}
for item in ["fan1", "fan2", "fan3", "fan4"]:
if item in miner_stats["STATS"][1].keys():
miner_fans["FANS"].append(
{"RPM": miner_stats["STATS"][1][item]})
if miner_summary:
if 'MHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = float(format(
round(
miner_summary['SUMMARY'][0]['MHS av'] / 1000000,
2), ".2f"))
elif 'GHS av' in miner_summary['SUMMARY'][0].keys():
hashrate = float(format(
round(miner_summary['SUMMARY'][0]['GHS av'] / 1000,
2),
".2f"))
else:
hashrate = 0
else:
hashrate = 0
fan_speeds = []
if miner_fans:
for fan in miner_fans["FANS"]:
fan_speeds.append(fan["RPM"])
while len(fan_speeds) < 4:
fan_speeds.append(0)
data = {"hashrate": hashrate,
"fans": fan_speeds,
"datetime": datetime.datetime.now().isoformat(),
"model": miner_model}
await websocket.send_json(data)
print(data)
await asyncio.sleep(settings["graph_data_sleep_time"])
except asyncio.exceptions.TimeoutError:
data = {"error": "The miner is not responding."}
await websocket.send_json(data)
await asyncio.sleep(.5)
except KeyError as e:
print(e)
data = {
"error": "The miner returned unusable/unsupported data."}
await websocket.send_json(data)
await asyncio.sleep(.5)
except WebSocketDisconnect:
print("Websocket disconnected.")
except websockets.exceptions.ConnectionClosedOK:
pass

View File

@@ -0,0 +1,26 @@
from fastapi import Request, APIRouter
from tools.web_monitor.template import templates
from tools.web_monitor.func import get_current_miner_list
from .ws import router as ws_router
router = APIRouter()
router.include_router(ws_router)
@router.get("/")
def scan(request: Request):
return templates.TemplateResponse("scan.html", {
"request": request,
"cur_miners": get_current_miner_list()
})
@router.post("/add_miners")
async def add_miners_scan(request: Request):
miners = await request.json()
with open("miner_list.txt", "a+") as file:
for miner_ip in miners["miners"]:
file.write(miner_ip + "\n")
return scan

View File

@@ -0,0 +1,37 @@
import asyncio
from fastapi import WebSocket
from network import MinerNetwork
from tools.web_monitor.func import get_current_miner_list
from tools.web_monitor.miner_factory import miner_factory
async def do_websocket_scan(websocket: WebSocket, network_ip: str):
cur_miners = get_current_miner_list()
try:
if "/" in network_ip:
network_ip, network_subnet = network_ip.split("/")
network = MinerNetwork(network_ip, mask=network_subnet)
else:
network = MinerNetwork(network_ip)
miner_generator = network.scan_network_generator()
miners = []
async for miner_ip in miner_generator:
if miner_ip and str(miner_ip) not in cur_miners:
miners.append(miner_ip)
get_miner_generator = miner_factory.get_miner_generator(miners)
all_miners = []
async for found_miner in get_miner_generator:
all_miners.append(
{"ip": found_miner.ip, "model": await found_miner.get_model()})
all_miners.sort(key=lambda x: x["ip"])
send_miners = []
for miner_ip in all_miners:
send_miners.append(
{"ip": str(miner_ip["ip"]), "model": miner_ip["model"]})
await websocket.send_json(send_miners)
await websocket.send_text("Done")
except asyncio.CancelledError:
raise

View File

@@ -0,0 +1,36 @@
import asyncio
import websockets.exceptions
from fastapi import WebSocket, WebSocketDisconnect, APIRouter
from tools.web_monitor.scan.func import do_websocket_scan
router = APIRouter()
@router.websocket("/ws")
async def websocket_scan(websocket: WebSocket):
await websocket.accept()
cur_task = None
try:
while True:
ws_data = await websocket.receive_text()
if "-Cancel-" in ws_data:
if cur_task:
cur_task.cancel()
print("Cancelling scan...")
try:
await cur_task
except asyncio.CancelledError:
cur_task = None
await websocket.send_text("Cancelled")
else:
cur_task = asyncio.create_task(
do_websocket_scan(websocket, ws_data))
if cur_task and cur_task.done():
cur_task = None
except WebSocketDisconnect:
print("Websocket disconnected.")
except websockets.exceptions.ConnectionClosedOK:
pass

View File

@@ -0,0 +1,33 @@
from fastapi import Request, APIRouter
from fastapi.responses import RedirectResponse
from tools.web_monitor.template import templates
from tools.web_monitor.func import get_current_miner_list
from tools.web_monitor.settings.func import get_current_settings, update_settings
router = APIRouter()
@router.route("/", methods=["GET", "POST"])
async def settings(request: Request):
return templates.TemplateResponse("settings.html", {
"request": request,
"cur_miners": get_current_miner_list(),
"settings": get_current_settings()
})
@router.post("/update")
async def update_settings_page(request: Request):
data = await request.form()
graph_data_sleep_time = data.get('graph_data_sleep_time')
miner_data_timeout = data.get('miner_data_timeout')
miner_identify_timeout = data.get('miner_identify_timeout')
new_settings = {
"graph_data_sleep_time": int(graph_data_sleep_time),
"miner_data_timeout": int(miner_data_timeout),
"miner_identify_timeout": int(miner_identify_timeout),
}
update_settings(new_settings)
return RedirectResponse(request.url_for("settings"))

View File

@@ -4,7 +4,7 @@ import os
def get_current_settings(): def get_current_settings():
try: try:
with open(os.path.join(os.getcwd(), "web_settings.toml"), "r") as settings_file: with open(os.path.join(os.getcwd(), "settings/web_settings.toml"), "r") as settings_file:
settings = toml.loads(settings_file.read()) settings = toml.loads(settings_file.read())
except: except:
settings = { settings = {
@@ -16,5 +16,5 @@ def get_current_settings():
def update_settings(settings): def update_settings(settings):
with open(os.path.join(os.getcwd(), "web_settings.toml"), "w") as settings_file: with open(os.path.join(os.getcwd(), "settings/web_settings.toml"), "w") as settings_file:
settings_file.write(toml.dumps(settings)) settings_file.write(toml.dumps(settings))

View File

@@ -62,7 +62,7 @@ main {
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
} }
@media (max-width: 767.98px) { @media (max-width: 991.98px) {
.sidebar { .sidebar {
top: 48px; top: 48px;
} }

View File

@@ -0,0 +1,6 @@
import os
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(
directory=os.path.join(os.path.dirname(__file__), "templates"))

View File

@@ -3,13 +3,14 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script>
<canvas id="line-chart" class="grad-border mt-3 mb-4" width="600" height="360"></canvas> <canvas id="line-chart" class="grad-border mt-3 mb-4" width="600" height="360"></canvas>
{% if cur_miners|length == 0 %}<a role="button" href="{{url_for('scan')}}" id="noMiners" class="w-100 btn btn-info">Click here to add miners.</a>{% endif %} {% if cur_miners|length == 0 %}<a role="button" href="/scan" id="noMiners" class="w-100 btn btn-info">Click here to add miners.</a>{% endif %}
<div id="errors"></div> <div id="errors"></div>
<script> <script>
var ws = new WebSocket("ws://localhost:80/dashboard/ws"); if (!window.WebSocket) alert("WebSocket not supported by this browser");
var ws = new WebSocket("ws://{{request.url.hostname}}:80/dashboard/ws");
let all_data = [] let all_data = []
let all_labels = [] let all_labels = []
ws.onmessage = function(event) { ws.onmessage = function(event) {
@@ -21,7 +22,7 @@ ws.onmessage = function(event) {
if (new_data["miners"][i].hasOwnProperty("error")) { if (new_data["miners"][i].hasOwnProperty("error")) {
if (!document.getElementById(new_data["miners"][i]["ip"] + "_error")) { if (!document.getElementById(new_data["miners"][i]["ip"] + "_error")) {
errors.innerHTML += "<div id='" + new_data["miners"][i]["ip"] + "_error" + errors.innerHTML += "<div id='" + new_data["miners"][i]["ip"] + "_error" +
"' class='d-flex align-items-center p-1 mb-1 ms-4 alert alert-danger'><strong class='p-0 m-0'>" + "' class='d-flex align-items-center p-1 mb-1 alert alert-danger'><strong class='p-0 m-0'>" +
new_data["miners"][i]["ip"] + ": " + new_data["miners"][i]["ip"] + ": " +
new_data["miners"][i]["error"] + new_data["miners"][i]["error"] +
"</strong><div class='spinner-border spinner-border-sm ms-auto'></div></div>" "</strong><div class='spinner-border spinner-border-sm ms-auto'></div></div>"

View File

@@ -4,7 +4,7 @@
<script src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script>
<div class="row mt-2"> <div class="row mt-2">
<div class="col"> <div class="col">
<h2 class="ms-4">{{miner}}</h2> <h2 class="ms-3 mt-1">{{miner}}</h2>
</div> </div>
<div class="col"> <div class="col">
<div class="d-flex flex-row-reverse"> <div class="d-flex flex-row-reverse">
@@ -53,7 +53,7 @@
</div> </div>
<div class="d-flex align-items-center mt-4 ms-4 alert alert-secondary"> <div class="d-flex align-items-center mt-4 alert alert-secondary">
<div class="mx-auto">Model:</div> <div class="mx-auto">Model:</div>
<div class="mx-auto fw-bolder" id="minerModel">?</div> <div class="mx-auto fw-bolder" id="minerModel">?</div>
<div class="mx-auto" style="border-left: 1px solid grey; height: 50px;"></div> <div class="mx-auto" style="border-left: 1px solid grey; height: 50px;"></div>
@@ -61,13 +61,13 @@
<div class="mx-auto fw-bolder" id="minerHashrate">?</div> <div class="mx-auto fw-bolder" id="minerHashrate">?</div>
</div> </div>
<div id="errorContainer" class="d-flex align-items-center mt-4 ms-4 alert alert-danger invisible"> <div id="errorContainer" class="d-flex align-items-center mt-4 alert alert-danger invisible">
<strong id="errorCode"></strong> <strong id="errorCode"></strong>
<div class="spinner-border ms-auto"></div> <div class="spinner-border ms-auto"></div>
</div> </div>
<script> <script>
var ws = new WebSocket("ws://localhost:80/miner/{{miner}}/ws"); var ws = new WebSocket("ws://{{request.url.hostname}}:80/miner/{{miner}}/ws");
let all_data = [] let all_data = []
let all_labels = [] let all_labels = []
ws.onmessage = function(event) { ws.onmessage = function(event) {

View File

@@ -6,6 +6,8 @@
<link href="{{ url_for('static', path='/navbar.css')}}" rel="stylesheet"> <link href="{{ url_for('static', path='/navbar.css')}}" rel="stylesheet">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Title</title> <title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src * gap: data: blob: 'unsafe-inline' 'unsafe-eval' ws: wss:;">
</head> </head>
<body> <body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
@@ -31,9 +33,9 @@
</svg> </svg>
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> <header class="navbar navbar-dark sticky-top bg-dark flex-xl-nowrap p-0 shadow">
<a class="d-md-none col-md-3 col-lg-2 me-0 px-3" style="height: 50px;" href="#"></a> <a class="d-lg-none col-lg-3 col-xl-2 me-0 px-3" style="height: 50px;" href="#"></a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler position-absolute d-lg-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -47,31 +49,31 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<nav id="sidebarMenu" class="text-white bg-dark col-md-3 col-lg-2 d-md-block sidebar collapse"> <nav id="sidebarMenu" class="text-white bg-dark col-lg-3 col-xl-2 d-lg-block sidebar collapse">
<div class="position-sticky pt-3"> <div class="position-sticky pt-3">
<ul class="nav nav-pills flex-column"> <ul class="nav nav-pills flex-column">
<li class="nav-item mb-1 mx-2"> <li class="nav-item mb-1 mx-2">
<a href="{{url_for('dashboard')}}" class="nav-link {% if request.path == '/dashboard' %}active{% else %}text-white{% endif %}"> <a href="/dashboard" class="nav-link {% if request.path.strip('/') == 'dashboard' %}active{% else %}text-white{% endif %}">
<svg class="bi me-2" width="16" height="16"><use xlink:href="#dashboard"></use></svg> <svg class="bi me-2" width="16" height="16"><use xlink:href="#dashboard"></use></svg>
Dashboard Dashboard
</a> </a>
</li> </li>
<li class="nav-item mb-1 mx-2"> <li class="nav-item mb-1 mx-2">
<a href="" class="nav-link {% if request.path == '/scan' or request.path.split('/')[1] == 'miner' %}active{% else %}text-white{% endif %}" data-bs-toggle="collapse" data-bs-target="#miners-collapse" aria-expanded="false"> <a href="" class="nav-link {% if request.path.strip('/') == 'scan' or request.path.split('/')[1] == 'miner' %}active{% else %}text-white{% endif %}" data-bs-toggle="collapse" data-bs-target="#miners-collapse" aria-expanded="false">
<svg class="bi me-2" width="16" height="16"><use xlink:href="#miners"></use></svg> <svg class="bi me-2" width="16" height="16"><use xlink:href="#miners"></use></svg>
Miners Miners
</a> </a>
<div class="collapse mt-1" id="miners-collapse" style=""> <div class="collapse mt-1" id="miners-collapse" style="">
<ul id="navMiners" class="btn-toggle-nav overflow-auto list-unstyled fw-normal pb-1 small"> <ul id="navMiners" class="btn-toggle-nav overflow-auto list-unstyled fw-normal pb-1 small">
<li> <li>
<a href="{{url_for('scan')}}" class="nav-link {% if request.path == '/scan' %}active{% else %}text-white{% endif %}"> <a href="/scan" class="nav-link {% if request.path.strip('/') == 'scan' %}active{% else %}text-white{% endif %}">
<svg class="bi me-2 mt-1" width="16" height="16"><use xlink:href="#scan"></use></svg> <svg class="bi me-2 mt-1" width="16" height="16"><use xlink:href="#scan"></use></svg>
Add Miners Add Miners
</a> </a>
</li> </li>
{% for miner in cur_miners %} {% for miner in cur_miners %}
<li> <li>
<a href="{{url_for('miner')}}/{{miner}}" class="nav-link {% if request.path == '/miner/' + miner %}active{% else %}text-white{% endif %}"> <a href="/miner/{{miner}}" class="nav-link {% if request.path.strip('/') == 'miner/' + miner %}active{% else %}text-white{% endif %}">
<svg class="bi me-2 mt-1" width="16" height="16"><use xlink:href="#miner"></use></svg> <svg class="bi me-2 mt-1" width="16" height="16"><use xlink:href="#miner"></use></svg>
{{miner}} {{miner}}
</a> </a>
@@ -85,7 +87,7 @@
<li class="border-top my-3"></li> <li class="border-top my-3"></li>
<li class="nav-item mb-1 mx-2"> <li class="nav-item mb-1 mx-2">
<a href="/settings" class="nav-link {% if request.path == '/settings' %}active{% else %}text-white{% endif %}"> <a href="/settings" class="nav-link {% if request.path.strip('/') == 'settings' %}active{% else %}text-white{% endif %}">
<svg class="bi me-2" width="16" height="16"><use xlink:href="#settings"></use></svg> <svg class="bi me-2" width="16" height="16"><use xlink:href="#settings"></use></svg>
Settings Settings
</a> </a>
@@ -95,7 +97,7 @@
</nav> </nav>
<div class="col-md-9 ms-sm-auto col-lg-10 px-md-4 ps-4"> <div class="col-lg-9 ms-md-auto col-xl-10 px-lg-4 ps-4">
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}

View File

@@ -56,7 +56,7 @@
window.post = function(url, data) { window.post = function(url, data) {
return fetch(url, {method: "POST", headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}); return fetch(url, {method: "POST", headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)});
} }
var ws = new WebSocket("ws://localhost:80/scan/ws"); var ws = new WebSocket("ws://{{request.url.hostname}}:80/scan/ws");
ws.onmessage = function(event) { ws.onmessage = function(event) {
if (event.data == "Done") { if (event.data == "Done") {
document.getElementById("scanButton").innerHTML = "Scan" document.getElementById("scanButton").innerHTML = "Scan"