mirror of
https://github.com/Cian-H/I-Form_Server_Node_Deployer.git
synced 2025-12-23 06:32:08 +00:00
First fully functioning GUI
This commit is contained in:
@@ -41,6 +41,7 @@ mkdocs-material = "^9.4.8"
|
|||||||
[tool.poetry.group.gui.dependencies]
|
[tool.poetry.group.gui.dependencies]
|
||||||
flet = "^0.11.0"
|
flet = "^0.11.0"
|
||||||
psutil = "^5.9.6"
|
psutil = "^5.9.6"
|
||||||
|
bidict = "^0.22.1"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ def write_disk(disk: str) -> None:
|
|||||||
mounts=[config.CWD_MOUNT, Mount("/ignition_disk", disk, type="bind")],
|
mounts=[config.CWD_MOUNT, Mount("/ignition_disk", disk, type="bind")],
|
||||||
privileged=True,
|
privileged=True,
|
||||||
command=f"dd if={config.CWD_MOUNTDIR}/build/ignition.img of=/ignition_disk",
|
command=f"dd if={config.CWD_MOUNTDIR}/build/ignition.img of=/ignition_disk",
|
||||||
remove=config.CLEANUP_CONTAINERS,
|
remove=config.CLEANUP_IMAGES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from telnetlib import IP
|
from functools import wraps
|
||||||
|
from typing import Callable, Mapping
|
||||||
|
|
||||||
|
from bidict import frozenbidict
|
||||||
import flet as ft
|
import flet as ft
|
||||||
from httpx import get
|
from flet_core.form_field_control import FormFieldControl
|
||||||
from node_deployer.create_disk import IPAddress, create_ignition_disk
|
from node_deployer.create_disk import create_ignition_disk
|
||||||
import ipaddress
|
from node_deployer.ip_interface import IPAddress
|
||||||
|
|
||||||
from .disk_dropdown import disk_dropdown
|
from .disk_dropdown import disk_dropdown
|
||||||
|
|
||||||
@@ -11,56 +14,136 @@ def main(page: ft.Page) -> None:
|
|||||||
page.title = "I-Form Server Node Deployer"
|
page.title = "I-Form Server Node Deployer"
|
||||||
page.vertical_alignment = ft.MainAxisAlignment.CENTER
|
page.vertical_alignment = ft.MainAxisAlignment.CENTER
|
||||||
|
|
||||||
#TODO: Add a confirmation before actually writing to the disk
|
# TODO: Add a progress bar
|
||||||
#TODO: Add a private password field
|
# TODO: Finalise arrangement of fields
|
||||||
#TODO: Add a guard against invalid values
|
|
||||||
#TODO: Guard should trigger highlighting of the invalid fields
|
|
||||||
#TODO: Add a progress bar
|
|
||||||
#TODO: Finalise arrangement of fields
|
|
||||||
|
|
||||||
|
# These fields are used to get the parameters for the disk creation
|
||||||
disk, dd_element = disk_dropdown(tooltip="Select the disk to write to", label="Disk")
|
disk, dd_element = disk_dropdown(tooltip="Select the disk to write to", label="Disk")
|
||||||
hostname = ft.TextField(value="host", label="Hostname", text_align=ft.TextAlign.LEFT)
|
hostname = ft.TextField(value="host", label="Hostname", text_align=ft.TextAlign.LEFT)
|
||||||
password = ft.TextField(label="Password", text_align=ft.TextAlign.LEFT)
|
password = ft.TextField(
|
||||||
|
label="Password", password=True, can_reveal_password=True, text_align=ft.TextAlign.LEFT
|
||||||
|
)
|
||||||
switch_ip = ft.TextField(label="Switch IP", text_align=ft.TextAlign.LEFT)
|
switch_ip = ft.TextField(label="Switch IP", text_align=ft.TextAlign.LEFT)
|
||||||
switch_port = ft.TextField(label="Switch Port", value="4789", text_align=ft.TextAlign.LEFT)
|
switch_port = ft.TextField(label="Switch Port", value="4789", text_align=ft.TextAlign.LEFT)
|
||||||
swarm_token = ft.TextField(label="Swarm Token", text_align=ft.TextAlign.LEFT)
|
swarm_token = ft.TextField(label="Swarm Token", text_align=ft.TextAlign.LEFT)
|
||||||
|
|
||||||
|
# This wrapper validates the value of the field before passing it to the function
|
||||||
|
def validate_value(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped():
|
||||||
|
out = func()
|
||||||
|
if out:
|
||||||
|
return out
|
||||||
|
else:
|
||||||
|
# TODO: Highlight the invalid field
|
||||||
|
raise NotImplementedError("Invalid field value path not implemented")
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
# The following closures are used to get the values of the fields as the correct datatype
|
||||||
|
@validate_value
|
||||||
def get_disk() -> str:
|
def get_disk() -> str:
|
||||||
return disk.value if disk.value is not None else ""
|
return disk.value if disk.value is not None else ""
|
||||||
|
|
||||||
|
@validate_value
|
||||||
def get_hostname() -> str:
|
def get_hostname() -> str:
|
||||||
return hostname.value if hostname.value is not None else ""
|
return hostname.value if hostname.value is not None else ""
|
||||||
|
|
||||||
|
@validate_value
|
||||||
def get_password() -> str:
|
def get_password() -> str:
|
||||||
return password.value if password.value is not None else ""
|
return password.value if password.value is not None else ""
|
||||||
|
|
||||||
|
@validate_value
|
||||||
def get_switch_ip() -> IPAddress:
|
def get_switch_ip() -> IPAddress:
|
||||||
return ipaddress.ip_address(switch_ip.value if switch_ip.value is not None else "0.0.0.0")
|
return IPAddress(switch_ip.value if switch_ip.value is not None else "0.0.0.0")
|
||||||
|
|
||||||
|
@validate_value
|
||||||
def get_switch_port() -> int:
|
def get_switch_port() -> int:
|
||||||
return int(switch_port.value if switch_port.value is not None else "0")
|
return int(switch_port.value if switch_port.value is not None else "0")
|
||||||
|
|
||||||
|
@validate_value
|
||||||
def get_swarm_token() -> str:
|
def get_swarm_token() -> str:
|
||||||
return swarm_token.value if swarm_token.value is not None else ""
|
return swarm_token.value if swarm_token.value is not None else ""
|
||||||
|
|
||||||
def trigger_disk_creation(_):
|
# A bidirectional dictionary gives us a stateless bidirectional map between
|
||||||
raise NotImplementedError
|
# fields and their fetch functions
|
||||||
create_ignition_disk(
|
field_fetch_map: Mapping[FormFieldControl, Callable] = frozenbidict(
|
||||||
disk=get_disk(),
|
{
|
||||||
hostname=get_hostname(),
|
disk: get_disk,
|
||||||
password=get_password(),
|
hostname: get_hostname,
|
||||||
switch_ip=get_switch_ip(),
|
password: get_password,
|
||||||
switch_port=get_switch_port(),
|
switch_ip: get_switch_ip,
|
||||||
swarm_token=get_swarm_token(),
|
switch_port: get_switch_port,
|
||||||
|
swarm_token: get_swarm_token,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# This button triggers the confirmation popup before calling the disk creation function
|
||||||
|
def confirm_disk_creation(_):
|
||||||
|
# Fetch the values of the fields
|
||||||
|
disk_val: str = field_fetch_map[disk]()
|
||||||
|
hostname_val: str = field_fetch_map[hostname]()
|
||||||
|
password_val: str = field_fetch_map[password]()
|
||||||
|
switch_ip_val: IPAddress = field_fetch_map[switch_ip]()
|
||||||
|
switch_port_val: int = field_fetch_map[switch_port]()
|
||||||
|
swarm_token_val: str = field_fetch_map[swarm_token]()
|
||||||
|
|
||||||
|
# The following closures build the confirmation popup
|
||||||
|
# Also: nested closures, eww. It feels dirty, but maintains the functional style
|
||||||
|
# and most of its benefits. I'm tempting the functional gods to punish me with
|
||||||
|
# side-effects here though...
|
||||||
|
def close_dlg(_):
|
||||||
|
dlg.open = False
|
||||||
|
page.update()
|
||||||
|
|
||||||
|
# This closure is called when the confirm disk creation button is pressed
|
||||||
|
def trigger_disk_creation(_):
|
||||||
|
dlg.title = None
|
||||||
|
dlg.content = ft.Row(
|
||||||
|
controls=[
|
||||||
|
ft.Text("Creating disk..."),
|
||||||
|
ft.ProgressRing(),
|
||||||
|
],
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
)
|
||||||
|
dlg.actions = []
|
||||||
|
page.update()
|
||||||
|
create_ignition_disk(
|
||||||
|
disk=disk_val,
|
||||||
|
hostname=hostname_val,
|
||||||
|
password=password_val,
|
||||||
|
switch_ip=switch_ip_val,
|
||||||
|
switch_port=switch_port_val,
|
||||||
|
swarm_token=swarm_token_val,
|
||||||
|
)
|
||||||
|
close_dlg(None)
|
||||||
|
|
||||||
|
dlg = ft.AlertDialog(
|
||||||
|
modal=True,
|
||||||
|
title=ft.Text("Please confirm"),
|
||||||
|
content=ft.Text(f"Overwrite disk {disk_val}?"),
|
||||||
|
actions=[
|
||||||
|
ft.TextButton("Yes", on_click=trigger_disk_creation),
|
||||||
|
ft.TextButton("No", on_click=close_dlg),
|
||||||
|
],
|
||||||
|
actions_alignment=ft.MainAxisAlignment.END,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finally, we open the dialog popup
|
||||||
|
page.dialog = dlg
|
||||||
|
dlg.open = True
|
||||||
|
page.update()
|
||||||
|
|
||||||
|
disk_creation_dialog = ft.FilledButton(
|
||||||
|
text="Create Ignition Disk",
|
||||||
|
on_click=confirm_disk_creation,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then, we arrange the fields into rows and columns
|
||||||
disk_row = ft.Row(
|
disk_row = ft.Row(
|
||||||
controls=[
|
controls=[
|
||||||
dd_element,
|
dd_element,
|
||||||
ft.FilledButton(
|
disk_creation_dialog,
|
||||||
text="Create Ignition Disk",
|
|
||||||
on_click=trigger_disk_creation,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
alignment=ft.MainAxisAlignment.CENTER,
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
@@ -78,12 +161,13 @@ def main(page: ft.Page) -> None:
|
|||||||
switch_ip,
|
switch_ip,
|
||||||
switch_port,
|
switch_port,
|
||||||
],
|
],
|
||||||
alignment=ft.MainAxisAlignment.CENTER
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
|
|
||||||
page.add(
|
stacked_rows = ft.Column(
|
||||||
ft.Column(
|
|
||||||
[disk_row, node_row, switch_row, swarm_token],
|
[disk_row, node_row, switch_row, swarm_token],
|
||||||
alignment=ft.MainAxisAlignment.CENTER,
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
# Finally, we add the rows to the page
|
||||||
|
page.add(stacked_rows)
|
||||||
|
|||||||
Reference in New Issue
Block a user