Merge branch 'pyqt_gui_cfg_util'
This commit is contained in:
@@ -1 +1,10 @@
|
||||
from tools.cfg_util.cfg_util_sg import main
|
||||
from .ui import ui
|
||||
import asyncio
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(ui())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
78
tools/cfg_util/commands/__init__.py
Normal file
78
tools/cfg_util/commands/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util.layout import window
|
||||
from tools.cfg_util.tables import TableManager
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
|
||||
|
||||
@disable_buttons("Flashing Lights")
|
||||
async def btn_light(ip_idxs: list):
|
||||
table_manager = TableManager()
|
||||
_table = window["cmd_table"].Widget
|
||||
iids = _table.get_children()
|
||||
for idx in ip_idxs:
|
||||
item = _table.item(iids[idx])
|
||||
ip = item["values"][0]
|
||||
new_light_val = not table_manager.data[ip]["Light"]
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
if new_light_val:
|
||||
success = await miner.fault_light_on()
|
||||
else:
|
||||
success = await miner.fault_light_off()
|
||||
if success:
|
||||
table_manager.data[ip]["Light"] = new_light_val
|
||||
table_manager.data[ip]["Command Output"] = "Fault Light command succeeded."
|
||||
else:
|
||||
table_manager.data[ip]["Command Output"] = "Fault Light command failed."
|
||||
table_manager.update_tables()
|
||||
|
||||
|
||||
@disable_buttons("Rebooting")
|
||||
async def btn_reboot(ip_idxs: list):
|
||||
table_manager = TableManager()
|
||||
_table = window["cmd_table"].Widget
|
||||
iids = _table.get_children()
|
||||
for idx in ip_idxs:
|
||||
item = _table.item(iids[idx])
|
||||
ip = item["values"][0]
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
success = await miner.reboot()
|
||||
if success:
|
||||
table_manager.data[ip]["Command Output"] = "Reboot command succeeded."
|
||||
else:
|
||||
table_manager.data[ip]["Command Output"] = "Reboot command failed."
|
||||
table_manager.update_tables()
|
||||
|
||||
|
||||
@disable_buttons("Restarting Backend")
|
||||
async def btn_backend(ip_idxs: list):
|
||||
table_manager = TableManager()
|
||||
_table = window["cmd_table"].Widget
|
||||
iids = _table.get_children()
|
||||
for idx in ip_idxs:
|
||||
item = _table.item(iids[idx])
|
||||
ip = item["values"][0]
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
success = await miner.restart_backend()
|
||||
if success:
|
||||
table_manager.data[ip][
|
||||
"Command Output"
|
||||
] = "Restart Backend command succeeded."
|
||||
else:
|
||||
table_manager.data[ip]["Command Output"] = "Restart Backend command failed."
|
||||
table_manager.update_tables()
|
||||
|
||||
|
||||
@disable_buttons("Sending Command")
|
||||
async def btn_command(ip_idxs: list, command: str):
|
||||
table_manager = TableManager()
|
||||
_table = window["cmd_table"].Widget
|
||||
iids = _table.get_children()
|
||||
for idx in ip_idxs:
|
||||
item = _table.item(iids[idx])
|
||||
ip = item["values"][0]
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
success = await miner.send_ssh_command(command)
|
||||
if not isinstance(success, str):
|
||||
success = f"Command {command} failed."
|
||||
table_manager.data[ip]["Command Output"] = success
|
||||
table_manager.update_tables()
|
||||
160
tools/cfg_util/configure/__init__.py
Normal file
160
tools/cfg_util/configure/__init__.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import PySimpleGUI as sg
|
||||
from config.bos import bos_config_convert
|
||||
import time
|
||||
from tools.cfg_util.layout import window, update_prog_bar
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
from miners.miner_factory import MinerFactory
|
||||
import asyncio
|
||||
from settings import CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS
|
||||
from tools.cfg_util.general import update_miners_data
|
||||
|
||||
|
||||
progress_bar_len = 0
|
||||
|
||||
|
||||
@disable_buttons("Importing")
|
||||
async def btn_import(table, selected):
|
||||
if not len(selected) > 0:
|
||||
return
|
||||
ip = [window[table].Values[row][0] for row in selected][0]
|
||||
miner = await MinerFactory().get_miner(ip)
|
||||
await miner.get_config()
|
||||
config = miner.config
|
||||
window["cfg_config_txt"].update(config)
|
||||
|
||||
|
||||
@disable_buttons("Configuring")
|
||||
async def btn_config(table, selected, config: str, last_oct_ip: bool):
|
||||
ips = [window[table].Values[row][0] for row in selected]
|
||||
await send_config(ips, config, last_oct_ip)
|
||||
|
||||
|
||||
async def send_config(ips: list, config: str, last_octet_ip: bool):
|
||||
global progress_bar_len
|
||||
progress_bar_len = 0
|
||||
await update_prog_bar(progress_bar_len, max=(2 * len(ips)))
|
||||
get_miner_genenerator = MinerFactory().get_miner_generator(ips)
|
||||
all_miners = []
|
||||
async for miner in get_miner_genenerator:
|
||||
all_miners.append(miner)
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
|
||||
config_sender_generator = send_config_generator(
|
||||
all_miners, config, last_octet_ip_user=last_octet_ip
|
||||
)
|
||||
async for _config_sender in config_sender_generator:
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
await asyncio.sleep(3)
|
||||
await update_miners_data(ips)
|
||||
|
||||
|
||||
async def send_config_generator(miners: list, config, last_octet_ip_user: bool):
|
||||
loop = asyncio.get_event_loop()
|
||||
config_tasks = []
|
||||
for miner in miners:
|
||||
if len(config_tasks) >= CONFIG_THREADS:
|
||||
configured = asyncio.as_completed(config_tasks)
|
||||
config_tasks = []
|
||||
for sent_config in configured:
|
||||
yield await sent_config
|
||||
config_tasks.append(
|
||||
loop.create_task(miner.send_config(config, ip_user=last_octet_ip_user))
|
||||
)
|
||||
configured = asyncio.as_completed(config_tasks)
|
||||
for sent_config in configured:
|
||||
yield await sent_config
|
||||
|
||||
|
||||
def generate_config(username: str, workername: str, v2_allowed: bool):
|
||||
if username and workername:
|
||||
user = f"{username}.{workername}"
|
||||
elif username and not workername:
|
||||
user = username
|
||||
else:
|
||||
return
|
||||
|
||||
if v2_allowed:
|
||||
url_1 = "stratum2+tcp://v2.us-east.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt"
|
||||
url_2 = "stratum2+tcp://v2.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt"
|
||||
url_3 = "stratum+tcp://stratum.slushpool.com:3333"
|
||||
else:
|
||||
url_1 = "stratum+tcp://ca.stratum.slushpool.com:3333"
|
||||
url_2 = "stratum+tcp://us-east.stratum.slushpool.com:3333"
|
||||
url_3 = "stratum+tcp://stratum.slushpool.com:3333"
|
||||
|
||||
config = {
|
||||
"group": [
|
||||
{
|
||||
"name": "group",
|
||||
"quota": 1,
|
||||
"pool": [
|
||||
{"url": url_1, "user": user, "password": "123"},
|
||||
{"url": url_2, "user": user, "password": "123"},
|
||||
{"url": url_3, "user": user, "password": "123"},
|
||||
],
|
||||
}
|
||||
],
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": "Antminer S9",
|
||||
"generator": "upstream_config_util",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"temp_control": {
|
||||
"target_temp": 80.0,
|
||||
"hot_temp": 90.0,
|
||||
"dangerous_temp": 120.0,
|
||||
},
|
||||
"autotuning": {"enabled": True, "psu_power_limit": 900},
|
||||
}
|
||||
window["cfg_config_txt"].update(bos_config_convert(config))
|
||||
|
||||
|
||||
async def generate_config_ui():
|
||||
generate_config_window = sg.Window(
|
||||
"Generate Config", generate_config_layout(), modal=True
|
||||
)
|
||||
while True:
|
||||
event, values = generate_config_window.read()
|
||||
if event in (None, "Close", sg.WIN_CLOSED):
|
||||
break
|
||||
if event == "generate_config_window_generate":
|
||||
if values["generate_config_window_username"]:
|
||||
generate_config(
|
||||
values["generate_config_window_username"],
|
||||
values["generate_config_window_workername"],
|
||||
values["generate_config_window_allow_v2"],
|
||||
)
|
||||
generate_config_window.close()
|
||||
break
|
||||
|
||||
|
||||
def generate_config_layout():
|
||||
config_layout = [
|
||||
[
|
||||
sg.Text(
|
||||
"Enter your pool username and password below to generate a config for SlushPool."
|
||||
)
|
||||
],
|
||||
[sg.Text("")],
|
||||
[
|
||||
sg.Text("Username:", size=(19, 1)),
|
||||
sg.InputText(
|
||||
key="generate_config_window_username", do_not_clear=True, size=(45, 1)
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Text("Worker Name (OPT):", size=(19, 1)),
|
||||
sg.InputText(
|
||||
key="generate_config_window_workername", do_not_clear=True, size=(45, 1)
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Text("Allow Stratum V2?:", size=(19, 1)),
|
||||
sg.Checkbox("", key="generate_config_window_allow_v2", default=True),
|
||||
],
|
||||
[sg.Button("Generate", key="generate_config_window_generate")],
|
||||
]
|
||||
return config_layout
|
||||
24
tools/cfg_util/decorators.py
Normal file
24
tools/cfg_util/decorators.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from tools.cfg_util.layout import window
|
||||
from tools.cfg_util.layout import BUTTON_KEYS
|
||||
|
||||
|
||||
def disable_buttons(status: str = ""):
|
||||
def decorator(func):
|
||||
# handle the inner function that the decorator is wrapping
|
||||
async def inner(*args, **kwargs):
|
||||
# disable the buttons
|
||||
for button in BUTTON_KEYS:
|
||||
window[button].Update(disabled=True)
|
||||
window["status"].update(status)
|
||||
|
||||
# call the original wrapped function
|
||||
await func(*args, **kwargs)
|
||||
|
||||
# re-enable the buttons after the wrapped function completes
|
||||
for button in BUTTON_KEYS:
|
||||
window[button].Update(disabled=False)
|
||||
window["status"].update("")
|
||||
|
||||
return inner
|
||||
|
||||
return decorator
|
||||
85
tools/cfg_util/general/__init__.py
Normal file
85
tools/cfg_util/general/__init__.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import asyncio
|
||||
import webbrowser
|
||||
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
from tools.cfg_util.layout import TABLE_KEYS
|
||||
from tools.cfg_util.layout import window, update_prog_bar
|
||||
from tools.cfg_util.tables import TableManager
|
||||
|
||||
progress_bar_len = 0
|
||||
|
||||
DEFAULT_DATA = [
|
||||
"Model",
|
||||
"Hostname",
|
||||
"Hashrate",
|
||||
"Temperature",
|
||||
"Pool User",
|
||||
"Pool 1",
|
||||
"Pool 1 User",
|
||||
"Pool 2",
|
||||
"Pool 2 User",
|
||||
"Wattage",
|
||||
"Split",
|
||||
]
|
||||
|
||||
|
||||
def btn_all(table, selected):
|
||||
if table in TABLE_KEYS["table"]:
|
||||
if len(selected) == len(window[table].Values):
|
||||
window[table].update(select_rows=())
|
||||
else:
|
||||
window[table].update(
|
||||
select_rows=([row for row in range(len(window[table].Values))])
|
||||
)
|
||||
|
||||
if table in TABLE_KEYS["tree"]:
|
||||
if len(selected) == len(window[table].Widget.get_children()):
|
||||
_tree = window[table]
|
||||
_tree.Widget.selection_set([])
|
||||
else:
|
||||
_tree = window[table]
|
||||
rows_to_select = [i for i in _tree.Widget.get_children()]
|
||||
_tree.Widget.selection_set(rows_to_select)
|
||||
|
||||
|
||||
def btn_web(table, selected):
|
||||
for row in selected:
|
||||
webbrowser.open("http://" + window[table].Values[row][0])
|
||||
|
||||
|
||||
@disable_buttons("Refreshing")
|
||||
async def btn_refresh(table, selected):
|
||||
ips = [window[table].Values[row][0] for row in selected]
|
||||
if not len(selected) > 0:
|
||||
ips = [window[table].Values[row][0] for row in range(len(window[table].Values))]
|
||||
|
||||
await update_miners_data(ips)
|
||||
|
||||
|
||||
async def update_miners_data(miners: list):
|
||||
data = []
|
||||
for miner in miners:
|
||||
_data = {}
|
||||
for key in DEFAULT_DATA:
|
||||
_data[key] = ""
|
||||
_data["IP"] = str(miner)
|
||||
data.append(_data)
|
||||
|
||||
TableManager().update_data(data)
|
||||
|
||||
global progress_bar_len
|
||||
progress_bar_len = 0
|
||||
await update_prog_bar(progress_bar_len, max=len(miners))
|
||||
data_generator = asyncio.as_completed(
|
||||
[_get_data(await MinerFactory().get_miner(miner)) for miner in miners]
|
||||
)
|
||||
for all_data in data_generator:
|
||||
data = await all_data
|
||||
TableManager().update_item(data)
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
|
||||
|
||||
async def _get_data(miner):
|
||||
return await miner.get_data()
|
||||
27
tools/cfg_util/imgs.py
Normal file
27
tools/cfg_util/imgs.py
Normal file
File diff suppressed because one or more lines are too long
302
tools/cfg_util/layout.py
Normal file
302
tools/cfg_util/layout.py
Normal file
@@ -0,0 +1,302 @@
|
||||
import PySimpleGUI as sg
|
||||
|
||||
from .imgs import WINDOW_ICON
|
||||
|
||||
sg.set_options(font=("Liberation Mono", 10))
|
||||
|
||||
TABLE_HEADERS = {
|
||||
"SCAN": [
|
||||
"IP",
|
||||
"Model",
|
||||
"Hostname",
|
||||
"Hashrate",
|
||||
"Temperature",
|
||||
"Pool User",
|
||||
"Wattage",
|
||||
],
|
||||
"CMD": ["IP", "Model", "Command Output"],
|
||||
"POOLS": [
|
||||
"IP",
|
||||
"Split",
|
||||
"Pool 1",
|
||||
"Pool 1 User",
|
||||
"Pool 2",
|
||||
"Pool 2 User",
|
||||
],
|
||||
"CONFIG": ["IP", "Model", "Pool 1 User"],
|
||||
}
|
||||
|
||||
TABLE_KEYS = {
|
||||
"table": ["scan_table", "pools_table", "cfg_table"],
|
||||
"tree": ["cmd_table"],
|
||||
}
|
||||
|
||||
MINER_COUNT_BUTTONS = [
|
||||
"scan_miner_count",
|
||||
"cmd_miner_count",
|
||||
"cfg_miner_count",
|
||||
"pools_miner_count",
|
||||
]
|
||||
|
||||
BUTTON_KEYS = [
|
||||
"btn_scan",
|
||||
"btn_cmd",
|
||||
"scan_all",
|
||||
"scan_refresh",
|
||||
"scan_web",
|
||||
"cmd_all",
|
||||
"cmd_light",
|
||||
"cmd_reboot",
|
||||
"cmd_backend",
|
||||
"pools_all",
|
||||
"pools_refresh",
|
||||
"pools_web",
|
||||
"cfg_import",
|
||||
"cfg_config",
|
||||
"cfg_generate",
|
||||
"cfg_all",
|
||||
"cfg_web",
|
||||
]
|
||||
|
||||
TABLE_HEIGHT = 27
|
||||
|
||||
IMAGE_COL_WIDTH = 6
|
||||
IP_COL_WIDTH = 17
|
||||
MODEL_COL_WIDTH = 15
|
||||
HOST_COL_WIDTH = 15
|
||||
HASHRATE_COL_WIDTH = 12
|
||||
TEMP_COL_WIDTH = 12
|
||||
USER_COL_WIDTH = 31
|
||||
WATTAGE_COL_WIDTH = 8
|
||||
SPLIT_COL_WIDTH = 6
|
||||
SCAN_COL_WIDTHS = [
|
||||
IP_COL_WIDTH,
|
||||
MODEL_COL_WIDTH,
|
||||
HOST_COL_WIDTH,
|
||||
HASHRATE_COL_WIDTH,
|
||||
TEMP_COL_WIDTH,
|
||||
USER_COL_WIDTH,
|
||||
WATTAGE_COL_WIDTH,
|
||||
]
|
||||
TABLE_TOTAL_WIDTH = sum(SCAN_COL_WIDTHS)
|
||||
|
||||
|
||||
async def update_prog_bar(count: int, max: int = None):
|
||||
bar = window["progress_bar"]
|
||||
bar.update_bar(count, max=max)
|
||||
if max:
|
||||
bar.maxlen = max
|
||||
if not hasattr(bar, "maxlen"):
|
||||
if not max:
|
||||
max = 100
|
||||
bar.maxlen = max
|
||||
|
||||
percent_done = 100 * (count / bar.maxlen)
|
||||
window["progress_percent"].Update(f"{round(percent_done, 2)} %")
|
||||
if percent_done == 100:
|
||||
window["progress_percent"].Update("")
|
||||
|
||||
|
||||
def get_scan_layout():
|
||||
scan_layout = [
|
||||
[
|
||||
sg.Text("Scan IP"),
|
||||
sg.InputText(key="scan_ip", size=(31, 1)),
|
||||
sg.Button("Scan", key="btn_scan"),
|
||||
sg.Push(),
|
||||
sg.Button(
|
||||
"Miners: 0",
|
||||
disabled=True,
|
||||
button_color=("black", "white smoke"),
|
||||
disabled_button_color=("black", "white smoke"),
|
||||
key="scan_miner_count",
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Button("ALL", key="scan_all"),
|
||||
sg.Button("REFRESH DATA", key="scan_refresh"),
|
||||
sg.Button("OPEN IN WEB", key="scan_web"),
|
||||
],
|
||||
[
|
||||
sg.Table(
|
||||
values=[],
|
||||
headings=[heading for heading in TABLE_HEADERS["SCAN"]],
|
||||
auto_size_columns=False,
|
||||
max_col_width=15,
|
||||
justification="center",
|
||||
key="scan_table",
|
||||
col_widths=SCAN_COL_WIDTHS,
|
||||
background_color="white",
|
||||
text_color="black",
|
||||
size=(TABLE_TOTAL_WIDTH, TABLE_HEIGHT),
|
||||
expand_x=True,
|
||||
enable_click_events=True,
|
||||
)
|
||||
],
|
||||
]
|
||||
return scan_layout
|
||||
|
||||
|
||||
def get_command_layout():
|
||||
data = sg.TreeData()
|
||||
col_widths = [
|
||||
IP_COL_WIDTH,
|
||||
MODEL_COL_WIDTH,
|
||||
TABLE_TOTAL_WIDTH - (IP_COL_WIDTH + MODEL_COL_WIDTH + IMAGE_COL_WIDTH + 4),
|
||||
]
|
||||
|
||||
command_layout = [
|
||||
[
|
||||
sg.Text("Custom Command"),
|
||||
sg.InputText(key="cmd_txt", expand_x=True),
|
||||
sg.Button("Send Command", key="btn_cmd"),
|
||||
sg.Push(),
|
||||
sg.Button(
|
||||
"Miners: 0",
|
||||
disabled=True,
|
||||
button_color=("black", "white smoke"),
|
||||
disabled_button_color=("black", "white smoke"),
|
||||
key="cmd_miner_count",
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Button("ALL", key="cmd_all"),
|
||||
sg.Button("LIGHT", key="cmd_light"),
|
||||
sg.Button("REBOOT", key="cmd_reboot"),
|
||||
sg.Button("RESTART BACKEND", key="cmd_backend"),
|
||||
],
|
||||
[
|
||||
sg.Tree(
|
||||
data,
|
||||
headings=[heading for heading in TABLE_HEADERS["CMD"]],
|
||||
auto_size_columns=False,
|
||||
max_col_width=15,
|
||||
justification="center",
|
||||
key="cmd_table",
|
||||
col_widths=col_widths,
|
||||
background_color="white",
|
||||
text_color="black",
|
||||
expand_x=True,
|
||||
expand_y=True,
|
||||
col0_heading="Light",
|
||||
col0_width=IMAGE_COL_WIDTH,
|
||||
enable_events=True,
|
||||
)
|
||||
],
|
||||
]
|
||||
return command_layout
|
||||
|
||||
|
||||
def get_pools_layout():
|
||||
pool_col_width = int((TABLE_TOTAL_WIDTH - (IP_COL_WIDTH + SPLIT_COL_WIDTH)) / 4)
|
||||
col_widths = [
|
||||
IP_COL_WIDTH,
|
||||
SPLIT_COL_WIDTH,
|
||||
pool_col_width + 5,
|
||||
pool_col_width - 5,
|
||||
pool_col_width + 5,
|
||||
pool_col_width - 5,
|
||||
]
|
||||
pools_layout = [
|
||||
[
|
||||
sg.Push(),
|
||||
sg.Button(
|
||||
"Miners: 0",
|
||||
disabled=True,
|
||||
button_color=("black", "white smoke"),
|
||||
disabled_button_color=("black", "white smoke"),
|
||||
key="pools_miner_count",
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Button("ALL", key="pools_all"),
|
||||
sg.Button("REFRESH DATA", key="pools_refresh"),
|
||||
sg.Button("OPEN IN WEB", key="pools_web"),
|
||||
],
|
||||
[
|
||||
sg.Table(
|
||||
values=[],
|
||||
headings=[heading for heading in TABLE_HEADERS["POOLS"]],
|
||||
auto_size_columns=False,
|
||||
max_col_width=15,
|
||||
justification="center",
|
||||
key="pools_table",
|
||||
background_color="white",
|
||||
text_color="black",
|
||||
col_widths=col_widths,
|
||||
size=(0, TABLE_HEIGHT),
|
||||
expand_x=True,
|
||||
enable_click_events=True,
|
||||
)
|
||||
],
|
||||
]
|
||||
return pools_layout
|
||||
|
||||
|
||||
def get_config_layout():
|
||||
config_layout = [
|
||||
[
|
||||
sg.Button("IMPORT", key="cfg_import"),
|
||||
sg.Button("CONFIG", key="cfg_config"),
|
||||
sg.Button("GENERATE", key="cfg_generate"),
|
||||
sg.Push(),
|
||||
sg.Button(
|
||||
"Miners: 0",
|
||||
disabled=True,
|
||||
button_color=("black", "white smoke"),
|
||||
disabled_button_color=("black", "white smoke"),
|
||||
key="cfg_miner_count",
|
||||
),
|
||||
],
|
||||
[
|
||||
sg.Button("ALL", key="cfg_all"),
|
||||
sg.Button("OPEN IN WEB", key="cfg_web"),
|
||||
sg.Push(),
|
||||
sg.Checkbox("Append IP to Username", key="cfg_append_ip"),
|
||||
],
|
||||
[
|
||||
sg.Table(
|
||||
values=[],
|
||||
headings=[heading for heading in TABLE_HEADERS["CONFIG"]],
|
||||
auto_size_columns=False,
|
||||
max_col_width=15,
|
||||
justification="center",
|
||||
key="cfg_table",
|
||||
background_color="white",
|
||||
text_color="black",
|
||||
col_widths=[
|
||||
IP_COL_WIDTH,
|
||||
MODEL_COL_WIDTH,
|
||||
TABLE_TOTAL_WIDTH - ((2 * 40) - 4),
|
||||
],
|
||||
size=(0, TABLE_HEIGHT),
|
||||
expand_x=True,
|
||||
enable_click_events=True,
|
||||
),
|
||||
sg.Multiline(size=(40, TABLE_HEIGHT + 1), key="cfg_config_txt"),
|
||||
],
|
||||
]
|
||||
return config_layout
|
||||
|
||||
|
||||
layout = [
|
||||
[
|
||||
sg.Text("", size=(20, 1), key="status"),
|
||||
sg.ProgressBar(
|
||||
max_value=100, size_px=(0, 20), expand_x=True, key="progress_bar"
|
||||
),
|
||||
sg.Text("", size=(20, 1), key="progress_percent", justification="r"),
|
||||
],
|
||||
[
|
||||
sg.TabGroup(
|
||||
[
|
||||
[sg.Tab("Scan", get_scan_layout())],
|
||||
[sg.Tab("Pools", get_pools_layout())],
|
||||
[sg.Tab("Configure", get_config_layout())],
|
||||
[sg.Tab("Command", get_command_layout())],
|
||||
]
|
||||
)
|
||||
],
|
||||
]
|
||||
|
||||
window = sg.Window("Upstream Config Util", layout, icon=WINDOW_ICON)
|
||||
95
tools/cfg_util/scan/__init__.py
Normal file
95
tools/cfg_util/scan/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import asyncio
|
||||
|
||||
from miners.miner_factory import MinerFactory
|
||||
from network import MinerNetwork
|
||||
from tools.cfg_util.decorators import disable_buttons
|
||||
from tools.cfg_util.layout import window, update_prog_bar
|
||||
from tools.cfg_util.tables import clear_tables, TableManager
|
||||
|
||||
progress_bar_len = 0
|
||||
|
||||
DEFAULT_DATA = [
|
||||
"Model",
|
||||
"Hostname",
|
||||
"Hashrate",
|
||||
"Temperature",
|
||||
"Pool User",
|
||||
"Pool 1",
|
||||
"Pool 1 User",
|
||||
"Pool 2",
|
||||
"Pool 2 User",
|
||||
"Wattage",
|
||||
"Split",
|
||||
]
|
||||
|
||||
|
||||
async def btn_all():
|
||||
table = "scan_table"
|
||||
window[table].update(
|
||||
select_rows=([row for row in range(len(window[table].Values))])
|
||||
)
|
||||
|
||||
|
||||
async def btn_scan(scan_ip: str):
|
||||
network = MinerNetwork("192.168.1.0")
|
||||
if scan_ip:
|
||||
if "/" in scan_ip:
|
||||
ip, mask = scan_ip.split("/")
|
||||
network = MinerNetwork(ip, mask=mask)
|
||||
else:
|
||||
network = MinerNetwork(scan_ip)
|
||||
asyncio.create_task(_scan_miners(network))
|
||||
|
||||
|
||||
@disable_buttons("Scanning")
|
||||
async def _scan_miners(network: MinerNetwork):
|
||||
clear_tables()
|
||||
scan_generator = network.scan_network_generator()
|
||||
MinerFactory().clear_cached_miners()
|
||||
|
||||
global progress_bar_len
|
||||
progress_bar_len = 0
|
||||
|
||||
network_size = len(network)
|
||||
await update_prog_bar(progress_bar_len, max=(3 * network_size))
|
||||
|
||||
scanned_miners = []
|
||||
async for miner in scan_generator:
|
||||
if miner:
|
||||
scanned_miners.append(miner)
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
|
||||
progress_bar_len += network_size - len(scanned_miners)
|
||||
await update_prog_bar(progress_bar_len)
|
||||
|
||||
get_miner_genenerator = MinerFactory().get_miner_generator(scanned_miners)
|
||||
|
||||
resolved_miners = []
|
||||
async for found_miner in get_miner_genenerator:
|
||||
resolved_miners.append(found_miner)
|
||||
resolved_miners.sort(key=lambda x: x.ip)
|
||||
_data = {}
|
||||
for key in DEFAULT_DATA:
|
||||
_data[key] = ""
|
||||
_data["IP"] = str(found_miner.ip)
|
||||
TableManager().update_item(_data)
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
progress_bar_len += network_size - len(resolved_miners)
|
||||
await update_prog_bar(progress_bar_len)
|
||||
await _get_miners_data(resolved_miners)
|
||||
|
||||
|
||||
async def _get_miners_data(miners: list):
|
||||
global progress_bar_len
|
||||
data_generator = asyncio.as_completed([_get_data(miner) for miner in miners])
|
||||
for all_data in data_generator:
|
||||
data = await all_data
|
||||
TableManager().update_item(data)
|
||||
progress_bar_len += 1
|
||||
await update_prog_bar(progress_bar_len)
|
||||
|
||||
|
||||
async def _get_data(miner):
|
||||
return await miner.get_data()
|
||||
146
tools/cfg_util/tables.py
Normal file
146
tools/cfg_util/tables.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from tools.cfg_util.layout import (
|
||||
MINER_COUNT_BUTTONS,
|
||||
TABLE_KEYS,
|
||||
TABLE_HEADERS,
|
||||
window,
|
||||
)
|
||||
from tools.cfg_util.imgs import TkImages, LIGHT, FAULT_LIGHT
|
||||
import PySimpleGUI as sg
|
||||
import ipaddress
|
||||
|
||||
|
||||
def update_miner_count(count):
|
||||
for button in MINER_COUNT_BUTTONS:
|
||||
window[button].update(f"Miners: {count}")
|
||||
|
||||
|
||||
def update_tables(data: list or None = None):
|
||||
TableManager().update_data(data)
|
||||
|
||||
|
||||
def clear_tables():
|
||||
TableManager().clear_tables()
|
||||
|
||||
|
||||
async def update_tree(data: list):
|
||||
for item in data:
|
||||
if not item.get("IP"):
|
||||
continue
|
||||
table_manager = TableManager()
|
||||
table_manager.update_tree_by_key(item, "IP")
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class TableManager(metaclass=Singleton):
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.images = TkImages()
|
||||
self.data = {}
|
||||
self.sort_key = "IP"
|
||||
self.sort_reverse = False
|
||||
|
||||
def update_data(self, data: list):
|
||||
if not data:
|
||||
return
|
||||
|
||||
for line in data:
|
||||
self.update_item(line)
|
||||
|
||||
def update_sort_key(self, sort_key):
|
||||
if self.sort_key == sort_key:
|
||||
self.sort_reverse = not self.sort_reverse
|
||||
self.sort_key = sort_key
|
||||
self.update_tables()
|
||||
|
||||
def update_item(self, data: dict):
|
||||
if not data or data == {} or not data.get("IP"):
|
||||
return
|
||||
|
||||
if not data.get("Light"):
|
||||
data["Light"] = False
|
||||
|
||||
if not data["IP"] in self.data.keys():
|
||||
self.data[data["IP"]] = {}
|
||||
|
||||
for key in data.keys():
|
||||
self.data[data["IP"]][key] = data[key]
|
||||
|
||||
self.update_tables()
|
||||
|
||||
def update_tables(self):
|
||||
tables = {
|
||||
"SCAN": [["" for _ in TABLE_HEADERS["SCAN"]] for _ in self.data],
|
||||
"CMD": [["" for _ in TABLE_HEADERS["CMD"]] for _ in self.data],
|
||||
"POOLS": [["" for _ in TABLE_HEADERS["POOLS"]] for _ in self.data],
|
||||
"CONFIG": [["" for _ in TABLE_HEADERS["CONFIG"]] for _ in self.data],
|
||||
}
|
||||
|
||||
ip_sorted_keys = sorted(self.data.keys(), key=lambda x: ipaddress.ip_address(x))
|
||||
sorted_keys = sorted(
|
||||
ip_sorted_keys, reverse=self.sort_reverse, key=lambda x: self._get_sort(x)
|
||||
)
|
||||
|
||||
for data_idx, key in enumerate(sorted_keys):
|
||||
item = self.data[key]
|
||||
keys = item.keys()
|
||||
|
||||
if "Hashrate" in keys:
|
||||
if not isinstance(item["Hashrate"], str):
|
||||
item[
|
||||
"Hashrate"
|
||||
] = f"{format(float(item['Hashrate']), '.2f').rjust(6, ' ')} TH/s"
|
||||
for key in keys:
|
||||
for table in TABLE_HEADERS.keys():
|
||||
for idx, header in enumerate(TABLE_HEADERS[table]):
|
||||
if key == header:
|
||||
tables[table][data_idx][idx] = item[key]
|
||||
|
||||
window["scan_table"].update(tables["SCAN"])
|
||||
window["pools_table"].update(tables["POOLS"])
|
||||
window["cfg_table"].update(tables["CONFIG"])
|
||||
|
||||
treedata = sg.TreeData()
|
||||
for idx, item in enumerate(tables["CMD"]):
|
||||
ico = LIGHT
|
||||
if self.data[item[0]]["Light"]:
|
||||
ico = FAULT_LIGHT
|
||||
treedata.insert("", idx, "", item, icon=ico)
|
||||
|
||||
window["cmd_table"].update(treedata)
|
||||
|
||||
update_miner_count(len(self.data))
|
||||
|
||||
def _get_sort(self, data_key: str):
|
||||
if self.sort_key == "IP":
|
||||
return ipaddress.ip_address(self.data[data_key]["IP"])
|
||||
|
||||
if self.sort_key == "Hashrate":
|
||||
return float(
|
||||
self.data[data_key]["Hashrate"].replace(" ", "").replace("TH/s", "")
|
||||
)
|
||||
|
||||
if self.sort_key in ["Wattage", "Temperature"]:
|
||||
if isinstance(self.data[data_key][self.sort_key], str):
|
||||
if self.sort_reverse:
|
||||
return -100000000 # large negative number to place it at the bottom
|
||||
else:
|
||||
return 1000000000 # large number to place it at the bottom
|
||||
|
||||
return self.data[data_key][self.sort_key]
|
||||
|
||||
def clear_tables(self):
|
||||
self.data = {}
|
||||
for table in TABLE_KEYS["table"]:
|
||||
window[table].update([])
|
||||
for tree in TABLE_KEYS["tree"]:
|
||||
window[tree].update(sg.TreeData())
|
||||
update_miner_count(0)
|
||||
122
tools/cfg_util/ui.py
Normal file
122
tools/cfg_util/ui.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import PySimpleGUI as sg
|
||||
import asyncio
|
||||
import sys
|
||||
from tools.cfg_util.imgs import TkImages
|
||||
from tools.cfg_util.scan import btn_scan
|
||||
from tools.cfg_util.commands import (
|
||||
btn_light,
|
||||
btn_reboot,
|
||||
btn_backend,
|
||||
btn_command,
|
||||
)
|
||||
from tools.cfg_util.configure import (
|
||||
generate_config_ui,
|
||||
btn_import,
|
||||
btn_config,
|
||||
)
|
||||
from tools.cfg_util.layout import window
|
||||
from tools.cfg_util.general import btn_all, btn_web, btn_refresh
|
||||
from tools.cfg_util.tables import TableManager
|
||||
import tkinter as tk
|
||||
|
||||
|
||||
async def ui():
|
||||
window.read(0)
|
||||
|
||||
# create images used in the table, they will not show if not saved here
|
||||
tk_imgs = TkImages()
|
||||
|
||||
# left justify hostnames
|
||||
window["scan_table"].Widget.column(2, anchor=tk.W)
|
||||
|
||||
# cmd table sort event
|
||||
# window["cmd_table"].Widget.bind("<Button-1>", lambda x: print("clicked"))
|
||||
|
||||
while True:
|
||||
event, value = window.read(0)
|
||||
if event in (None, "Close", sg.WIN_CLOSED):
|
||||
sys.exit()
|
||||
|
||||
if isinstance(event, tuple):
|
||||
if len(window["scan_table"].Values) > 0:
|
||||
if event[0].endswith("_table"):
|
||||
if event[2][0] == -1:
|
||||
mgr = TableManager()
|
||||
table = window[event[0]].Widget
|
||||
mgr.update_sort_key(table.heading(event[2][1])["text"])
|
||||
|
||||
# scan tab
|
||||
|
||||
if event == "scan_all":
|
||||
_table = "scan_table"
|
||||
btn_all(_table, value[_table])
|
||||
if event == "scan_web":
|
||||
_table = "scan_table"
|
||||
btn_web(_table, value[_table])
|
||||
if event == "scan_refresh":
|
||||
_table = "scan_table"
|
||||
asyncio.create_task(btn_refresh(_table, value[_table]))
|
||||
if event == "btn_scan":
|
||||
asyncio.create_task(btn_scan(value["scan_ip"]))
|
||||
|
||||
# pools tab
|
||||
if event == "pools_all":
|
||||
_table = "pools_table"
|
||||
btn_all(_table, value[_table])
|
||||
if event == "pools_web":
|
||||
_table = "pools_table"
|
||||
btn_web(_table, value[_table])
|
||||
if event == "pools_refresh":
|
||||
_table = "pools_table"
|
||||
asyncio.create_task(btn_refresh(_table, value[_table]))
|
||||
|
||||
# configure tab
|
||||
if event == "cfg_all":
|
||||
_table = "cfg_table"
|
||||
btn_all(_table, value[_table])
|
||||
if event == "cfg_web":
|
||||
_table = "cfg_table"
|
||||
btn_web(_table, value[_table])
|
||||
if event == "cfg_generate":
|
||||
await generate_config_ui()
|
||||
if event == "cfg_import":
|
||||
_table = "cfg_table"
|
||||
asyncio.create_task(btn_import(_table, value[_table]))
|
||||
if event == "cfg_config":
|
||||
_table = "cfg_table"
|
||||
asyncio.create_task(
|
||||
btn_config(
|
||||
_table,
|
||||
value[_table],
|
||||
value["cfg_config_txt"],
|
||||
value["cfg_append_ip"],
|
||||
)
|
||||
)
|
||||
|
||||
# commands tab
|
||||
if event == "cmd_all":
|
||||
_table = "cmd_table"
|
||||
btn_all(_table, value[_table])
|
||||
if event == "cmd_light":
|
||||
_table = "cmd_table"
|
||||
_ips = value[_table]
|
||||
asyncio.create_task(btn_light(_ips))
|
||||
if event == "cmd_reboot":
|
||||
_table = "cmd_table"
|
||||
_ips = value[_table]
|
||||
asyncio.create_task(btn_reboot(_ips))
|
||||
if event == "cmd_backend":
|
||||
_table = "cmd_table"
|
||||
_ips = value[_table]
|
||||
asyncio.create_task(btn_backend(_ips))
|
||||
if event == "btn_cmd":
|
||||
_table = "cmd_table"
|
||||
_ips = value[_table]
|
||||
asyncio.create_task(btn_command(_ips, value["cmd_txt"]))
|
||||
|
||||
if event == "__TIMEOUT__":
|
||||
await asyncio.sleep(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(ui())
|
||||
1
tools/cfg_util_old/__init__.py
Normal file
1
tools/cfg_util_old/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from tools.cfg_util_old.cfg_util_sg import main
|
||||
@@ -1,14 +1,8 @@
|
||||
# TODO: Add Logging
|
||||
|
||||
# TODO: Add an option to append the last octet of the IP
|
||||
# address to the workername when configuring
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from tools.cfg_util.cfg_util_sg.ui import ui
|
||||
from tools.cfg_util_old.cfg_util_sg.ui import ui
|
||||
|
||||
|
||||
# initialize logger and get settings
|
||||
@@ -1,4 +1,4 @@
|
||||
from tools.cfg_util.cfg_util_sg.layout import window
|
||||
from tools.cfg_util_old.cfg_util_sg.layout import window
|
||||
|
||||
|
||||
def disable_buttons(func):
|
||||
@@ -6,8 +6,8 @@ import time
|
||||
import aiofiles
|
||||
import toml
|
||||
|
||||
from tools.cfg_util.cfg_util_sg.func.ui import update_ui_with_data
|
||||
from tools.cfg_util.cfg_util_sg.layout import window
|
||||
from tools.cfg_util_old.cfg_util_sg.func.ui import update_ui_with_data
|
||||
from tools.cfg_util_old.cfg_util_sg.layout import window
|
||||
from config.bos import bos_config_convert, general_config_convert_bos
|
||||
|
||||
|
||||
@@ -80,13 +80,13 @@ async def import_config_file(file_location):
|
||||
else:
|
||||
async with aiofiles.open(file_location, mode="r") as file:
|
||||
config = await file.read()
|
||||
await update_ui_with_data("config", await bos_config_convert(toml.loads(config)))
|
||||
await update_ui_with_data("config", bos_config_convert(toml.loads(config)))
|
||||
await update_ui_with_data("status", "")
|
||||
|
||||
|
||||
async def export_config_file(file_location, config):
|
||||
await update_ui_with_data("status", "Exporting")
|
||||
config = toml.dumps(await general_config_convert_bos(config))
|
||||
config = toml.dumps(general_config_convert_bos(config))
|
||||
config = toml.loads(config)
|
||||
config["format"]["generator"] = "upstream_config_util"
|
||||
config["format"]["timestamp"] = int(time.time())
|
||||
@@ -5,16 +5,16 @@ import warnings
|
||||
import logging
|
||||
|
||||
from API import APIError
|
||||
from tools.cfg_util.cfg_util_sg.func.parse_data import safe_parse_api_data
|
||||
from tools.cfg_util.cfg_util_sg.func.ui import (
|
||||
from tools.cfg_util_old.cfg_util_sg.func.parse_data import safe_parse_api_data
|
||||
from tools.cfg_util_old.cfg_util_sg.func.ui import (
|
||||
update_ui_with_data,
|
||||
update_prog_bar,
|
||||
set_progress_bar_len,
|
||||
)
|
||||
from tools.cfg_util.cfg_util_sg.layout import window
|
||||
from tools.cfg_util_old.cfg_util_sg.layout import window
|
||||
from miners.miner_factory import MinerFactory
|
||||
from config.bos import bos_config_convert
|
||||
from tools.cfg_util.cfg_util_sg.func.decorators import disable_buttons
|
||||
from tools.cfg_util_old.cfg_util_sg.func.decorators import disable_buttons
|
||||
from settings import (
|
||||
CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS,
|
||||
CFG_UTIL_REBOOT_THREADS as REBOOT_THREADS,
|
||||
@@ -580,4 +580,4 @@ async def generate_config(username, workername, v2_allowed):
|
||||
},
|
||||
"autotuning": {"enabled": True, "psu_power_limit": 900},
|
||||
}
|
||||
window["config"].update(await bos_config_convert(config))
|
||||
window["config"].update(bos_config_convert(config))
|
||||
@@ -1,7 +1,7 @@
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
from tools.cfg_util.cfg_util_sg.layout import window
|
||||
from tools.cfg_util_old.cfg_util_sg.layout import window
|
||||
|
||||
import pyperclip
|
||||
|
||||
@@ -3,12 +3,12 @@ import sys
|
||||
import PySimpleGUI as sg
|
||||
import tkinter as tk
|
||||
|
||||
from tools.cfg_util.cfg_util_sg.layout import (
|
||||
from tools.cfg_util_old.cfg_util_sg.layout import (
|
||||
window,
|
||||
generate_config_layout,
|
||||
send_ssh_cmd_layout,
|
||||
)
|
||||
from tools.cfg_util.cfg_util_sg.func.miners import (
|
||||
from tools.cfg_util_old.cfg_util_sg.func.miners import (
|
||||
send_config,
|
||||
miner_light,
|
||||
refresh_data,
|
||||
@@ -19,15 +19,15 @@ from tools.cfg_util.cfg_util_sg.func.miners import (
|
||||
reboot_miners,
|
||||
send_miners_ssh_commands,
|
||||
)
|
||||
from tools.cfg_util.cfg_util_sg.func.files import (
|
||||
from tools.cfg_util_old.cfg_util_sg.func.files import (
|
||||
import_iplist,
|
||||
import_config_file,
|
||||
export_iplist,
|
||||
export_config_file,
|
||||
export_csv,
|
||||
)
|
||||
from tools.cfg_util.cfg_util_sg.func.decorators import disable_buttons
|
||||
from tools.cfg_util.cfg_util_sg.func.ui import (
|
||||
from tools.cfg_util_old.cfg_util_sg.func.decorators import disable_buttons
|
||||
from tools.cfg_util_old.cfg_util_sg.func.ui import (
|
||||
sort_data,
|
||||
copy_from_table,
|
||||
table_select_all,
|
||||
Reference in New Issue
Block a user