add base files for web interface

This commit is contained in:
UpstreamData
2022-03-25 16:02:50 -06:00
parent 8864aa7b4b
commit a0311e3ce3
7 changed files with 544 additions and 4 deletions

View File

@@ -17,7 +17,7 @@ def index(request: Request):
@router.get("/dashboard")
def dashboard(request: Request):
print()
return templates.TemplateResponse(
"index.html", {"request": request, "cur_miners": get_current_miner_list()}
)
return templates.TemplateResponse("index.html", {
"request": request,
"cur_miners": get_current_miner_list()
})

View File

@@ -0,0 +1,55 @@
from fastapi import FastAPI, WebSocket, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
import uvicorn
import os
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/public", StaticFiles(
directory=os.path.join(os.path.dirname(__file__), "public")), name="public")
templates = Jinja2Templates(
directory=os.path.join(os.path.dirname(__file__), "templates"))
class ConnectionManager:
_instance = None
def __init__(self):
self.connections = []
def __new__(cls):
if not cls._instance:
cls._instance = super(
ConnectionManager,
cls
).__new__(cls)
return cls._instance
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.connections.append(websocket)
async def broadcast_json(self, data: str):
for connection in self.connections:
await connection.json(data)
@app.websocket("/ws")
async def ws(websocket: WebSocket):
await ConnectionManager().connect(websocket)
@app.get("/")
def dashboard(request: Request):
return templates.TemplateResponse("index.html", {
"request": request,
})
if __name__ == '__main__':
uvicorn.run("app:app", host="0.0.0.0", port=80)

View File

@@ -0,0 +1,261 @@
import { sio } from "./sio.js"
import { generate_graphs } from "./generate_graphs.js"
function pauseMiner(ip, checkbox) {
// if the checkbox is checked we need to pause, unchecked is unpause
if (checkbox.checked){
sio.emit("pause", ip)
} else if (!(checkbox.check)) {
sio.emit("unpause", ip)
}
}
function checkPause(ip, checkbox) {
// make sure the checkbox exists, removes an error
if (checkbox) {
// get status of pause and set checkbox to this status
sio.emit("check_pause", ip, (result) => {
checkbox.checked = result
}
);
}
}
function lightMiner(ip, checkbox) {
// if the checkbox is checked turn the light on, otherwise off
if (checkbox.checked){
sio.emit("light", ip)
} else if (!(checkbox.check)) {
sio.emit("unlight", ip)
}
}
function checkLight(ip, checkbox) {
// make sure the checkbox exists, removes an error
if (checkbox) {
// get status of light and set checkbox to this status
sio.emit("check_light", ip, (result) => {
checkbox.checked = result
}
);
}
}
export function generate_layout(data_graph) {
// get the container for all the charts and data
var container_all = document.getElementById('chart_container');
// empty the container out
container_all.innerHTML = ""
data_graph.miners.forEach(function(miner) {
// create main div column for all data to sit inside
var column = document.createElement('div');
column.className = "col border border-dark p-3"
// create IP address header
var header = document.createElement('button');
header.className = "text-center btn btn-primary w-100"
header.onclick = function(){window.open("http://" + miner.IP, '_blank');}
header.innerHTML += miner.IP
// add the header to col first
column.append(header)
// create light button container
var container_light = document.createElement('div');
container_light.className = "form-check form-switch d-flex justify-content-evenly"
// create light button
var light_switch = document.createElement('input');
light_switch.type = "checkbox"
light_switch.id = "light_" + miner.IP
light_switch.className = "form-check-input"
// check if the light is turned on and add click listener
checkLight(miner.IP, light_switch);
light_switch.addEventListener("click", function(){lightMiner(miner.IP, light_switch);}, false);
// add a light label to the button
var label_light = document.createElement("label");
label_light.setAttribute("for", "light_" + miner.IP);
label_light.innerHTML = "Light";
// add the button and label to the container
container_light.append(light_switch)
container_light.append(label_light)
if (miner.hasOwnProperty('text')) {
// create text row
var row_text = document.createElement('div');
row_text.className = "row"
// create text container
var text_container = document.createElement('div')
text_container.className = "col w-100 p-3"
// create text area for data
var text_area = document.createElement('textarea');
text_area.rows = "10"
text_area.className = "form-control"
text_area.style = "font-size: 12px"
text_area.disabled = true
text_area.readonly = true
// add data to the text area
var text = miner.text
text += text_area.innerHTML
text_area.innerHTML = text
// add the text area to the row
row_text.append(text_area)
// create a row for buttons
var row_buttons = document.createElement('div');
row_buttons.className = "row mt-3"
// create pause button container
var container_pause = document.createElement('div');
container_pause.className = "form-check form-switch d-flex justify-content-evenly"
// create the pause button
var pause_switch = document.createElement('input');
pause_switch.type = "checkbox"
pause_switch.id = "pause_" + miner.IP
pause_switch.className = "form-check-input"
// check if it is paused and add the click listener
checkPause(miner.IP, pause_switch);
pause_switch.addEventListener("click", function(){pauseMiner(miner.IP, pause_switch);}, false);
// add a pause label
var label_pause = document.createElement("label");
label_pause.setAttribute("for", "pause_" + miner.IP);
label_pause.innerHTML = "Pause";
// add the label and button to the container
container_pause.append(pause_switch);
container_pause.append(label_pause);
text_container.append(row_text);
// add the container to the row
row_buttons.append(container_pause);
if (miner.Light == "show") {
// add the light container to the row
row_buttons.append(container_light)
}
//add the row to the main column
column.append(text_container);
column.append(row_buttons);
// add the column onto the page
container_all.append(column);
} else {
// get fan rpm
var fan_rpm_1 = miner.Fans.fan_0.RPM;
var fan_rpm_2 = miner.Fans.fan_1.RPM;
// create hr canvas
var hr_canvas = document.createElement('canvas');
// create temp canvas
var temp_canvas = document.createElement('canvas');
// create fan 1 title
var fan_1_title = document.createElement('p');
fan_1_title.innerHTML += "Fan L: " + fan_rpm_1 + " RPM";
fan_1_title.className = "text-center"
// create fan 2 title
var fan_2_title = document.createElement('p');
fan_2_title.innerHTML += "Fan R: " + fan_rpm_2 + " RPM";
fan_2_title.className = "text-center"
// create fan 1 canvas
var fan_1_canvas = document.createElement('canvas');
// create fan 2 canvas
var fan_2_canvas = document.createElement('canvas');
// create row for hr and temp data
var row_hr = document.createElement('div');
row_hr.className = "row"
// create row for titles of fans
var row_fan_title = document.createElement('div');
row_fan_title.className = "row"
// create row for fan graphs
var row_fan = document.createElement('div');
row_fan.className = "row"
// create hr container
var container_col_hr = document.createElement('div');
container_col_hr.className = "col w-50 ps-0 pe-4"
// create temp container
var container_col_temp = document.createElement('div');
container_col_temp.className = "col w-50 ps-0 pe-4"
// create fan title 1 container
var container_col_title_fan_1 = document.createElement('div');
container_col_title_fan_1.className = "col"
// create fan title 2 container
var container_col_title_fan_2 = document.createElement('div');
container_col_title_fan_2.className = "col"
// create fan 1 data container
var container_col_fan_1 = document.createElement('div');
container_col_fan_1.className = "col w-50 ps-3 pe-1"
// create fan 2 data container
var container_col_fan_2 = document.createElement('div');
container_col_fan_2.className = "col w-50 ps-3 pe-1"
// append canvases to the appropriate container columns
container_col_hr.append(hr_canvas)
container_col_temp.append(temp_canvas)
container_col_title_fan_1.append(fan_1_title)
container_col_title_fan_2.append(fan_2_title)
container_col_fan_1.append(fan_1_canvas)
container_col_fan_2.append(fan_2_canvas)
// add container columns to the correct rows
row_hr.append(container_col_hr)
row_hr.append(container_col_temp)
row_fan_title.append(container_col_title_fan_1)
row_fan_title.append(container_col_title_fan_2)
row_fan.append(container_col_fan_1)
row_fan.append(container_col_fan_2)
// append the rows to the columns
column.append(row_hr)
column.append(row_fan_title)
column.append(row_fan)
// create a row for buttons
var row_buttons = document.createElement('div');
row_buttons.className = "row mt-3"
if (miner.Light == "show") {
// add the light container to the row
row_buttons.append(container_light)
}
// add the row to the main column
column.append(row_buttons)
// add the column to the page
container_all.append(column);
// generate the graphs
generate_graphs(miner, hr_canvas, temp_canvas, fan_1_canvas, fan_2_canvas);
}
});
}

View File

@@ -0,0 +1,7 @@
import {generate_layout} from "./create_layout.js"
// when miner data is sent
ws.onmessage = function(event) {
// generate the layout of the page
generate_layout(JSON.parse(event.data));
});

View File

@@ -0,0 +1,135 @@
import { options_hr, options_temp, options_fans } from "./graph_options.js";
// generate graphs used for the layout
export function generate_graphs(miner, hr_canvas, temp_canvas, fan_1_canvas, fan_2_canvas) {
var hr_data = []
var count = 0
// get data on all 3 boards
for (const board_num of [6, 7, 8]) {
// check if that board exists in the data
if (("board_" + board_num) in miner.HR) {
// set the key used to get the data
var key = "board_"+board_num
// add the hr info to the hr_data
hr_data.push({label: board_num, data: [miner.HR[key].HR], backgroundColor: []})
// set the colors to be used in the graphs (shades of blue)
if (board_num == 6) {
hr_data[count].backgroundColor = ["rgba(0, 19, 97, 1)"]
} else if (board_num == 7) {
hr_data[count].backgroundColor = ["rgba(0, 84, 219, 1)"]
} else if (board_num == 8) {
hr_data[count].backgroundColor = ["rgba(36, 180, 224, 1)"]
}
count += 1
}
}
// create the hr chart
var chart_hr = new Chart(hr_canvas, {
type: "bar",
data: {
labels: ["Hashrate"],
// data from above
datasets: hr_data
},
// options imported from graph_options.js
options: options_hr
});
var temps_data = []
// get temp data for each board
for (const board_num of [6, 7, 8]) {
// check if the board is in the keys list
if (("board_" + board_num) in miner.Temps) {
// set the key to be used to access the data
key = "board_"+board_num
// add chip and board temps to the temps_data along with colors
temps_data.push({label: board_num + " Chip", data: [miner.Temps[key].Chip], backgroundColor: ["rgba(6, 92, 39, 1)"]});
temps_data.push({label: board_num + " Board", data: [miner.Temps[key].Board], backgroundColor: ["rgba(255, 15, 58, 1)"]});
}
}
var chart_temp = new Chart(temp_canvas, {
type: "bar",
data: {
labels: ["Temps"],
// data from above
datasets: temps_data
},
// options imported from graph_options.js
options: options_temp,
});
// get fan rpm
var fan_rpm_1 = miner.Fans.fan_0.RPM;
if (fan_rpm_1 == 0){
var secondary_col_1 = "rgba(97, 4, 4, 1)"
} else {
var secondary_col_1 = "rgba(199, 199, 199, 1)"
}
var fan_rpm_2 = miner.Fans.fan_1.RPM;
if (fan_rpm_2 == 0){
var secondary_col_2 = "rgba(97, 4, 4, 1)"
} else {
var secondary_col_2 = "rgba(199, 199, 199, 1)"
}
// set the fan data to be rpm and the rest to go up to 6000
var fan_data_1 = [fan_rpm_1, (6000-fan_rpm_1)];
// create the fan 1 chart
var chart_fan_1 = new Chart(fan_1_canvas, {
type: "doughnut",
data: {
labels: ["Fan L"],
datasets: [
{
// data from above, no colors included
data: fan_data_1,
// add colors
backgroundColor: [
"rgba(103, 0, 221, 1)",
secondary_col_1
]
},
]
},
// options imported from graph_options.js
options: options_fans
});
var fan_data_2 = [fan_rpm_2, (6000-fan_rpm_2)];
// create the fan 2 chart
var chart_fan_2 = new Chart(fan_2_canvas, {
type: "doughnut",
data: {
labels: ["Fan R"],
datasets: [
{
// data from above, no colors included
data: fan_data_2,
// add colors
backgroundColor: [
"rgba(103, 0, 221, 1)",
secondary_col_2
]
},
]
},
// options imported from graph_options.js
options: options_fans
});
}

View File

@@ -0,0 +1,59 @@
// All options for creation of graphs in ./generate_graphs.js
export var options_hr = {
animation: {
duration: 0,
},
responsive: true,
aspectRatio: .75,
plugins: {
legend: {
display: false,
}
},
scales: {
y: {
ticks: { stepSize: .6 },
min: 0,
suggestedMax: 3.6,
grid: {
color: function(context) {
if (context.tick.value == 2.4) {
return "rgba(0, 0, 0, 1)";
} else if (context.tick.value > 2.4) {
return "rgba(103, 221, 0, 1)";
} else if (context.tick.value < 2.4) {
return "rgba(221, 0, 103, 1)";
}
}
}
}
}
};
export var options_temp = {
animation: {
duration: 0,
},
responsive: true,
plugins: {
legend: {
display: false,
}
},
aspectRatio: .75,
};
export var options_fans = {
animation: {
duration: 0,
},
aspectRatio: 1.5,
events: [],
responsive: true,
plugins: {
legend: {
display: false,
}
}
};

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Include chart.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js" integrity="sha512-Wt1bJGtlnMtGP0dqNFH1xlkLBNpEodaiQ8ZN5JLA5wpc1sUlk/O5uuOMNgvzddzkpvZ9GLyYNa8w2s7rqiTk5Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Include JQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- Include Bootstrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.min.js" integrity="sha384-cn7l7gDp0eyniUwwAZgrzD06kc/tftFf19TOAs2zVinnD/C7E91j9yyk5//jjpt/" crossorigin="anonymous"></script>
<!-- Include socketIO -->
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="py-2">
<div class="container">
<div id="chart_container" class="row row-cols-1 row-cols-sm-2 row-cols-md-4" style="height:1500px;"></div>
</div>
</div>
</body>
</html>