reformatted file structure and reformatted for phones, as well as fixed web sockets for remote devices
This commit is contained in:
@@ -1,252 +1,24 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import ipaddress
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
import websockets.exceptions
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from network import MinerNetwork
|
||||
from tools.web_monitor.miner_factory import miner_factory
|
||||
from tools.web_monitor.web_settings import get_current_settings, update_settings
|
||||
from tools.web_monitor.dashboard import router as dashboard_router
|
||||
from tools.web_monitor.miner import router as miner_router
|
||||
from tools.web_monitor.scan import router as scan_router
|
||||
from tools.web_monitor.settings import router as settings_router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
|
||||
templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "templates"))
|
||||
|
||||
|
||||
@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.mount("/static", StaticFiles(
|
||||
directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
|
||||
|
||||
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")
|
||||
@@ -256,70 +28,5 @@ async def remove_all_miners(request: Request):
|
||||
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__":
|
||||
uvicorn.run("app:app", host="0.0.0.0", port=80)
|
||||
|
||||
24
tools/web_monitor/dashboard/__init__.py
Normal file
24
tools/web_monitor/dashboard/__init__.py
Normal 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()
|
||||
})
|
||||
43
tools/web_monitor/dashboard/func.py
Normal file
43
tools/web_monitor/dashboard/func.py
Normal 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."}
|
||||
37
tools/web_monitor/dashboard/ws.py
Normal file
37
tools/web_monitor/dashboard/ws.py
Normal 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
|
||||
12
tools/web_monitor/func/__init__.py
Normal file
12
tools/web_monitor/func/__init__.py
Normal 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
|
||||
23
tools/web_monitor/miner/__init__.py
Normal file
23
tools/web_monitor/miner/__init__.py
Normal 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
|
||||
})
|
||||
16
tools/web_monitor/miner/remove.py
Normal file
16
tools/web_monitor/miner/remove.py
Normal 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'))
|
||||
103
tools/web_monitor/miner/ws.py
Normal file
103
tools/web_monitor/miner/ws.py
Normal 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
|
||||
26
tools/web_monitor/scan/__init__.py
Normal file
26
tools/web_monitor/scan/__init__.py
Normal 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
|
||||
37
tools/web_monitor/scan/func.py
Normal file
37
tools/web_monitor/scan/func.py
Normal 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
|
||||
36
tools/web_monitor/scan/ws.py
Normal file
36
tools/web_monitor/scan/ws.py
Normal 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
|
||||
33
tools/web_monitor/settings/__init__.py
Normal file
33
tools/web_monitor/settings/__init__.py
Normal 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"))
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
def get_current_settings():
|
||||
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())
|
||||
except:
|
||||
settings = {
|
||||
@@ -16,5 +16,5 @@ def get_current_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))
|
||||
@@ -62,7 +62,7 @@ main {
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
6
tools/web_monitor/template.py
Normal file
6
tools/web_monitor/template.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
|
||||
templates = Jinja2Templates(
|
||||
directory=os.path.join(os.path.dirname(__file__), "templates"))
|
||||
@@ -3,13 +3,14 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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_labels = []
|
||||
ws.onmessage = function(event) {
|
||||
@@ -21,7 +22,7 @@ ws.onmessage = function(event) {
|
||||
if (new_data["miners"][i].hasOwnProperty("error")) {
|
||||
if (!document.getElementById(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]["error"] +
|
||||
"</strong><div class='spinner-border spinner-border-sm ms-auto'></div></div>"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<h2 class="ms-4">{{miner}}</h2>
|
||||
<h2 class="ms-3 mt-1">{{miner}}</h2>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
@@ -53,7 +53,7 @@
|
||||
</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 fw-bolder" id="minerModel">?</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>
|
||||
|
||||
<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>
|
||||
<div class="spinner-border ms-auto"></div>
|
||||
</div>
|
||||
|
||||
<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_labels = []
|
||||
ws.onmessage = function(event) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<link href="{{ url_for('static', path='/navbar.css')}}" rel="stylesheet">
|
||||
<meta charset="UTF-8">
|
||||
<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>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
@@ -31,9 +33,9 @@
|
||||
</svg>
|
||||
|
||||
|
||||
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<a class="d-md-none col-md-3 col-lg-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">
|
||||
<header class="navbar navbar-dark sticky-top bg-dark flex-xl-nowrap p-0 shadow">
|
||||
<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-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>
|
||||
</button>
|
||||
|
||||
@@ -47,31 +49,31 @@
|
||||
|
||||
<div class="container-fluid">
|
||||
<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">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<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>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
Miners
|
||||
</a>
|
||||
<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">
|
||||
<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>
|
||||
Add Miners
|
||||
</a>
|
||||
</li>
|
||||
{% for miner in cur_miners %}
|
||||
<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>
|
||||
{{miner}}
|
||||
</a>
|
||||
@@ -85,7 +87,7 @@
|
||||
<li class="border-top my-3"></li>
|
||||
|
||||
<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>
|
||||
Settings
|
||||
</a>
|
||||
@@ -95,7 +97,7 @@
|
||||
</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 %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
window.post = function(url, 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) {
|
||||
if (event.data == "Done") {
|
||||
document.getElementById("scanButton").innerHTML = "Scan"
|
||||
|
||||
Reference in New Issue
Block a user