mirror of
https://github.com/Cian-H/I-Form_Server_Node_Deployer.git
synced 2025-12-23 06:32:08 +00:00
Did a bit of UI polishing
This commit is contained in:
BIN
src/node_deployer_gui/assets/logo_dark.png
Normal file
BIN
src/node_deployer_gui/assets/logo_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
src/node_deployer_gui/assets/logo_light.png
Normal file
BIN
src/node_deployer_gui/assets/logo_light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,5 +1,6 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from pathlib import Path
|
||||||
from typing import Callable, DefaultDict, Optional, Tuple
|
from typing import Callable, DefaultDict, Optional, Tuple
|
||||||
|
|
||||||
import flet as ft
|
import flet as ft
|
||||||
@@ -10,24 +11,61 @@ from .disk_dropdown import disk_dropdown
|
|||||||
from .types import CreateDiskArgs
|
from .types import CreateDiskArgs
|
||||||
|
|
||||||
|
|
||||||
|
# Hotkeys will be mapped using using a dict to avoid a big, slow if-elif-else chain
|
||||||
|
def _no_hotkey() -> Callable[[ft.KeyboardEvent], None]:
|
||||||
|
def dummy_func(e: ft.KeyboardEvent) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return dummy_func
|
||||||
|
|
||||||
|
|
||||||
|
HOTKEY_MAP: DefaultDict[str, Callable[[ft.KeyboardEvent], None]] = defaultdict(_no_hotkey)
|
||||||
|
|
||||||
|
|
||||||
def main(page: ft.Page) -> None:
|
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 logo
|
|
||||||
# TODO: Add a progress bar
|
# TODO: Add a progress bar
|
||||||
# TODO: Finalise arrangement of fields
|
|
||||||
# TODO: Add save/load functionality?
|
# TODO: Add save/load functionality?
|
||||||
|
|
||||||
|
# Lets start with the easiest part: a logo in the top left
|
||||||
|
if page.platform_brightness == ft.ThemeMode.DARK:
|
||||||
|
logo = ft.Image(str(Path(__file__).parent / "assets/logo_dark.png"), width=210)
|
||||||
|
else:
|
||||||
|
logo = ft.Image(str(Path(__file__).parent / "assets/logo_light.png"), width=210)
|
||||||
|
|
||||||
|
logo_container = ft.Container(
|
||||||
|
content=logo,
|
||||||
|
padding=10,
|
||||||
|
alignment=ft.alignment.top_left,
|
||||||
|
)
|
||||||
|
page.add(logo_container)
|
||||||
|
|
||||||
# These fields are used to get the parameters for the disk creation
|
# 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(
|
||||||
password = ft.TextField(
|
value="host", label="Hostname", text_align=ft.TextAlign.LEFT, width=page.window_width // 3
|
||||||
label="Password", password=True, can_reveal_password=True, text_align=ft.TextAlign.LEFT
|
)
|
||||||
|
password = ft.TextField(
|
||||||
|
label="Password",
|
||||||
|
password=True,
|
||||||
|
can_reveal_password=True,
|
||||||
|
text_align=ft.TextAlign.LEFT,
|
||||||
|
width=page.window_width // 3,
|
||||||
|
)
|
||||||
|
switch_ip = ft.TextField(
|
||||||
|
label="Switch IP", text_align=ft.TextAlign.LEFT, width=page.window_width // 3
|
||||||
|
)
|
||||||
|
switch_port = ft.TextField(
|
||||||
|
label="Switch Port",
|
||||||
|
value="4789",
|
||||||
|
text_align=ft.TextAlign.LEFT,
|
||||||
|
width=page.window_width // 3,
|
||||||
|
)
|
||||||
|
swarm_token = ft.TextField(
|
||||||
|
label="Swarm Token", text_align=ft.TextAlign.LEFT, width=int((2 * page.window_width) // 3)
|
||||||
)
|
)
|
||||||
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)
|
|
||||||
swarm_token = ft.TextField(label="Swarm Token", text_align=ft.TextAlign.LEFT)
|
|
||||||
|
|
||||||
# Add varnames, as they will be useful for unpacking later
|
# Add varnames, as they will be useful for unpacking later
|
||||||
disk.__varname__ = "disk"
|
disk.__varname__ = "disk"
|
||||||
@@ -38,7 +76,11 @@ def main(page: ft.Page) -> None:
|
|||||||
swarm_token.__varname__ = "swarm_token"
|
swarm_token.__varname__ = "swarm_token"
|
||||||
|
|
||||||
# This wrapper validates the value of the field before passing it to the function
|
# This wrapper validates the value of the field before passing it to the function
|
||||||
def validate_value[F](func: Callable[[], F]) -> Callable[[], Optional[F]]: # mypy PEP 695 support can't come quickly enough # noqa
|
def validate_value[
|
||||||
|
F
|
||||||
|
](func: Callable[[], F]) -> Callable[
|
||||||
|
[], Optional[F]
|
||||||
|
]: # mypy PEP 695 support can't come quickly enough # noqa
|
||||||
#! It is important that bool(F) evaluates to False if the value is invalid
|
#! It is important that bool(F) evaluates to False if the value is invalid
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped() -> Optional[F]:
|
def wrapped() -> Optional[F]:
|
||||||
@@ -125,7 +167,7 @@ def main(page: ft.Page) -> None:
|
|||||||
field.border_color = None
|
field.border_color = None
|
||||||
field.update()
|
field.update()
|
||||||
typed_val = target_type(value)
|
typed_val = target_type(value)
|
||||||
vals[varname] = typed_val # type: ignore #! This is a false positive, an invalid literal would have been caught by the if statement above
|
vals[varname] = typed_val # type: ignore #! This is a false positive, an invalid literal would have been caught by the if statement above
|
||||||
|
|
||||||
if invalid_values:
|
if invalid_values:
|
||||||
return
|
return
|
||||||
@@ -143,6 +185,11 @@ def main(page: ft.Page) -> None:
|
|||||||
# side-effects here though...
|
# side-effects here though...
|
||||||
def close_dlg(*_) -> None:
|
def close_dlg(*_) -> None:
|
||||||
dlg.open = False
|
dlg.open = False
|
||||||
|
if "Y" in HOTKEY_MAP.keys():
|
||||||
|
del HOTKEY_MAP["Y"]
|
||||||
|
if "N" in HOTKEY_MAP.keys():
|
||||||
|
del HOTKEY_MAP["N"]
|
||||||
|
HOTKEY_MAP["Enter"] = confirm_disk_creation
|
||||||
page.update()
|
page.update()
|
||||||
|
|
||||||
# This closure is called when the confirm disk creation button is pressed
|
# This closure is called when the confirm disk creation button is pressed
|
||||||
@@ -182,14 +229,20 @@ def main(page: ft.Page) -> None:
|
|||||||
actions_alignment=ft.MainAxisAlignment.END,
|
actions_alignment=ft.MainAxisAlignment.END,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Finally, we open the dialog popup
|
# Finally, we open the dialog popup and switch the hotkeys over
|
||||||
page.dialog = dlg
|
page.dialog = dlg
|
||||||
dlg.open = True
|
dlg.open = True
|
||||||
|
if "Enter" in HOTKEY_MAP.keys():
|
||||||
|
del HOTKEY_MAP["Enter"]
|
||||||
|
HOTKEY_MAP["Y"] = trigger_disk_creation
|
||||||
|
HOTKEY_MAP["N"] = close_dlg
|
||||||
page.update()
|
page.update()
|
||||||
|
|
||||||
|
|
||||||
disk_creation_dialog = ft.FilledButton(
|
disk_creation_dialog = ft.FilledButton(
|
||||||
text="Create Ignition Disk",
|
text="Create Ignition Disk",
|
||||||
on_click=confirm_disk_creation,
|
on_click=confirm_disk_creation,
|
||||||
|
width=page.window_width // 3,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Then, we arrange the fields into rows and columns
|
# Then, we arrange the fields into rows and columns
|
||||||
@@ -217,34 +270,32 @@ def main(page: ft.Page) -> None:
|
|||||||
alignment=ft.MainAxisAlignment.CENTER,
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
swarm_token_row = ft.Row(
|
||||||
|
controls=[
|
||||||
|
swarm_token,
|
||||||
|
],
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
)
|
||||||
|
|
||||||
stacked_rows = ft.Column(
|
stacked_rows = ft.Column(
|
||||||
[disk_row, node_row, switch_row, swarm_token],
|
[disk_row, node_row, switch_row, swarm_token_row],
|
||||||
alignment=ft.MainAxisAlignment.CENTER,
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Finally, we finish constructing the UI by adding the rows to the page
|
# Finally, we finish constructing the UI by adding the rows to the page
|
||||||
page.add(stacked_rows)
|
page.add(stacked_rows)
|
||||||
|
|
||||||
# As a final task, we define the hotkey events
|
# As a final task, we define the opening screen hotkey events
|
||||||
# We do this using a dict to avoid a big, slow if-elif-else chain
|
HOTKEY_MAP["Enter"] = confirm_disk_creation
|
||||||
def no_hotkey() -> Callable[[ft.KeyboardEvent], None]:
|
|
||||||
def dummy_func(e: ft.KeyboardEvent) -> None:
|
|
||||||
pass
|
|
||||||
return dummy_func
|
|
||||||
|
|
||||||
def quit_app(e: ft.KeyboardEvent) -> None:
|
def quit_app(e: ft.KeyboardEvent) -> None:
|
||||||
if e.ctrl:
|
if e.ctrl:
|
||||||
page.window_close()
|
page.window_close()
|
||||||
|
|
||||||
hotkey_map: DefaultDict[str, Callable[[ft.KeyboardEvent], None]] = defaultdict(
|
HOTKEY_MAP["Q"] = quit_app
|
||||||
no_hotkey,
|
|
||||||
Enter=confirm_disk_creation,
|
|
||||||
Q=quit_app,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_key_press(e: ft.KeyboardEvent) -> None:
|
def on_key_press(e: ft.KeyboardEvent) -> None:
|
||||||
print(e.key)
|
HOTKEY_MAP[e.key](e)
|
||||||
hotkey_map[e.key](e)
|
|
||||||
|
|
||||||
page.on_keyboard_event = on_key_press
|
page.on_keyboard_event = on_key_press
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user