19 Commits

Author SHA1 Message Date
6b412140d4 Merge pull request #8 from Konrni/main
Add copy feature , fix a small bug
2024-09-30 21:37:35 -04:00
61157457b8 refactor answer selection actions 2024-09-29 17:36:37 -04:00
46cae312c8 Merge branch 'main' of https://github.com/Konrni/autopve 2024-09-29 19:16:12 +02:00
aa04718374 revert answer naming changes to allow submiting previous name 2024-09-28 16:46:32 -04:00
4c6495c4ee allow escape at answer naming 2024-09-28 16:45:48 -04:00
8f00ab9570 add password_toggle_button, enable password viewing 2024-09-28 10:34:30 +02:00
32dec36cd7 don't "unselect" after one answer deletion 2024-09-28 10:27:46 +02:00
f9b5c0d153 simplify answer storage logic and enable copy 2024-09-27 21:23:22 -04:00
b3dc60d950 improved answer sanity check 2024-09-27 21:21:47 -04:00
f8e93cedf2 syntax change 2024-09-27 21:20:30 -04:00
074975650f fix bug, where save after unchanged name would delete answer content 2024-09-28 00:01:29 +02:00
7f654071d0 added copy feature for answers 2024-09-27 23:51:13 +02:00
0711b55ad8 Update README.md 2024-05-12 15:01:46 -04:00
779d25def5 added disk-setup specfic sanity checks 2024-05-12 14:17:43 -04:00
bba6410bbd bind settings methods to class 2024-05-12 14:15:52 -04:00
edc5987293 Merge pull request #2 from natankeddem/fixremoves
Fixes for removal of keys and restrictions.
2024-05-06 18:53:31 -04:00
2b6f0e41e6 fixed removal of restrictions 2024-05-06 18:50:44 -04:00
444c2d3182 fixed removal of keys 2024-05-06 18:50:13 -04:00
a069cfe410 Update README.md 2024-05-05 16:19:45 -04:00
5 changed files with 137 additions and 108 deletions

View File

@ -2,7 +2,7 @@
## Demo ## Demo
[autopve_demo.webm](https://github.com/natankeddem/autopve/assets/44515217/827bdd22-5311-43c1-9452-a56fa11998aa) https://github.com/natankeddem/autopve/assets/44515217/9439e2e2-a7bf-4677-aea8-0684318bea6c
## Information ## Information

View File

@ -44,6 +44,7 @@ class Drawer(object):
el.IButton(icon="add", on_click=self._display_answer_dialog) el.IButton(icon="add", on_click=self._display_answer_dialog)
self._buttons["remove"] = el.IButton(icon="remove", on_click=lambda: self._modify_answer("remove")) self._buttons["remove"] = el.IButton(icon="remove", on_click=lambda: self._modify_answer("remove"))
self._buttons["edit"] = el.IButton(icon="edit", on_click=lambda: self._modify_answer("edit")) self._buttons["edit"] = el.IButton(icon="edit", on_click=lambda: self._modify_answer("edit"))
self._buttons["content_copy"] = el.IButton(icon="content_copy", on_click=lambda: self._modify_answer("content_copy"))
ui.label(text="ANSWERS").classes("text-secondary") ui.label(text="ANSWERS").classes("text-secondary")
self._table = ( self._table = (
ui.table( ui.table(
@ -83,7 +84,7 @@ class Drawer(object):
self._table.add_rows({"name": name}) self._table.add_rows({"name": name})
self._table.visible = True self._table.visible = True
async def _display_answer_dialog(self, name=""): async def _display_answer_dialog(self, name="", copy=False):
save = None save = None
with ui.dialog() as answer_dialog, el.Card(): with ui.dialog() as answer_dialog, el.Card():
@ -100,6 +101,8 @@ class Drawer(object):
def answer_check(value: str) -> Optional[bool]: def answer_check(value: str) -> Optional[bool]:
spaceless = value.replace(" ", "") spaceless = value.replace(" ", "")
if len(spaceless) == 0:
return False
for invalid_value in all_answers: for invalid_value in all_answers:
if invalid_value == spaceless: if invalid_value == spaceless:
return False return False
@ -108,25 +111,28 @@ class Drawer(object):
def enter_submit(e: KeyEventArguments) -> None: def enter_submit(e: KeyEventArguments) -> None:
if e.key == "Enter" and save_ea.no_errors is True: if e.key == "Enter" and save_ea.no_errors is True:
answer_dialog.submit("save") answer_dialog.submit("save")
elif e.key == "Escape":
answer_dialog.close()
answer_input = el.VInput(label="answer", value=" ", invalid_characters="""'`"$\\;&<>|(){}""", invalid_values=all_answers, check=answer_check, max_length=20) answer_input = el.VInput(label="answer", value=" ", invalid_characters="""'`"$\\;&<>|(){}""", invalid_values=all_answers, check=answer_check, max_length=20)
save_ea = el.ErrorAggregator(answer_input) save_ea = el.ErrorAggregator(answer_input)
el.DButton("SAVE", on_click=lambda: answer_dialog.submit("save")).bind_enabled_from(save_ea, "no_errors") el.DButton("SAVE", on_click=lambda: answer_dialog.submit("save")).bind_enabled_from(save_ea, "no_errors")
ui.keyboard(on_key=enter_submit, ignore=[]) ui.keyboard(on_key=enter_submit, ignore=[])
answer_input.value = name answer_input.value = name
result = await answer_dialog result = await answer_dialog
if result == "save": answer = answer_input.value.strip()
answer = answer_input.value.strip() if result == "save" and name != answer:
if len(answer) > 0 and name != "Default": if name in storage.answers:
storage.answer(answer) storage.answers[answer] = storage.answer(name, copy=True)
if name in storage.answers: if copy is False:
storage.answers[answer] = storage.answer(name, copy=True)
del storage.answers[name] del storage.answers[name]
for row in self._table.rows: for row in self._table.rows:
if name == row["name"]: if name == row["name"]:
self._table.remove_rows(row) self._table.remove_rows(row)
self._add_answer_to_table(answer) else:
storage.answer(answer)
self._add_answer_to_table(answer)
def _modify_answer(self, mode): def _modify_answer(self, mode):
self._hide_content() self._hide_content()
@ -152,17 +158,20 @@ class Drawer(object):
async def _selected(self, e): async def _selected(self, e):
self._hide_content() self._hide_content()
if self._selection_mode == "edit": if len(e.selection) == 1:
if len(e.selection) > 0 and e.selection[0]["name"] != "Default": answer = e.selection[0]["name"]
await self._display_answer_dialog(name=e.selection[0]["name"]) if self._selection_mode == "content_copy":
if self._selection_mode == "remove": await self._display_answer_dialog(name=answer, copy=True)
if len(e.selection) > 0: self._modify_answer(None)
for row in e.selection: elif answer == "Default":
if row["name"] != "Default": self._table._props["selected"] = []
if row["name"] in storage.answers: elif self._selection_mode == "edit":
del storage.answers[row["name"]] await self._display_answer_dialog(name=answer)
self._table.remove_rows(row) self._modify_answer(None)
self._modify_answer(None) elif self._selection_mode == "remove":
if answer in storage.answers:
del storage.answers[answer]
self._table.remove_rows(e.selection[0])
async def _clicked(self, e): async def _clicked(self, e):
if "name" in e.args[1]: if "name" in e.args[1]:

View File

@ -14,92 +14,97 @@ class Setting(Tab):
super().__init__(answer, type=type) super().__init__(answer, type=type)
def _build(self): def _build(self):
self.key_picker() self.keys_controls()
def key_picker(self): def keys_controls(self):
def keys_controls(): with ui.column() as col:
with ui.column() as col: col.tailwind.width("[560px]").align_items("center")
col.tailwind.width("[560px]").align_items("center") with ui.card() as card:
with ui.card() as card: card.tailwind.width("full")
card.tailwind.width("full") key_select = ui.select(list(self.keys.keys()), label="key", new_value_mode="add", with_input=True)
key_select = ui.select(list(self.keys.keys()), label="key", new_value_mode="add", with_input=True) key_select.tailwind.width("full")
key_select.tailwind.width("full") with ui.row() as row:
row.tailwind.width("full").align_items("center").justify_content("between")
with ui.row() as row: with ui.row() as row:
row.tailwind.width("full").align_items("center").justify_content("between") row.tailwind.align_items("center")
with ui.row() as row: self.help = None
row.tailwind.align_items("center") key = el.FInput(label="key", on_change=lambda e: self.key_changed(e.value), read_only=True)
self.help = None key.bind_value_from(key_select)
key = el.FInput(label="key", on_change=lambda e: key_changed(e), read_only=True) with ui.button(icon="help"):
key.bind_value_from(key_select) self.help = ui.tooltip("NA")
ui.button(icon="add", on_click=lambda key=key: self.add_key(key.value))
ui.separator()
self._scroll = ui.scroll_area()
self._scroll.tailwind.width("full").height("[480px]")
items = storage.answer(self.answer)
if self.type is not None and self.type in items:
for key, value in items[self.type].items():
if isinstance(value, list):
self.add_key(key, "[" + ",".join(str(v) for v in value) + "]")
else:
self.add_key(key, str(value))
def add_key(self, key: str, value: str = ""):
if self.key_valid(key) is True:
with self._scroll:
with ui.row() as key_row:
key_row.tailwind.width("full").align_items("center").justify_content("between")
with ui.row() as row:
row.tailwind.align_items("center")
self._elements[key] = {
"control": el.FInput(
key,
password=True if key == "root_password" else False,
password_toggle_button=True if key == "root_password" else False,
autocomplete=self.keys[key]["options"] if key in self.keys and "options" in self.keys[key] else None,
on_change=lambda e, key=key: self.set_key(key, e.value),
),
"row": key_row,
}
self._elements[key]["control"].value = value
if key in self.keys:
with ui.button(icon="help"): with ui.button(icon="help"):
self.help = ui.tooltip("NA") ui.tooltip(self.keys[key]["description"])
ui.button(icon="add", on_click=lambda key=key: add_key(key.value)) ui.button(icon="remove", on_click=lambda _, key=key: self.remove_key(key))
ui.separator()
self._scroll = ui.scroll_area()
self._scroll.tailwind.width("full").height("[480px]")
items = storage.answer(self.answer)
if self.type is not None and self.type in items:
for key, value in items[self.type].items():
if isinstance(value, list):
add_key(key, "[" + ",".join(str(v) for v in value) + "]")
else:
add_key(key, str(value))
def add_key(key: str, value: str = ""): def key_valid(self, key: str) -> bool:
if key is not None and key != "" and key not in self._elements.keys(): if key is not None and key != "" and key not in self._elements.keys():
with self._scroll: return True
with ui.row() as key_row: return False
key_row.tailwind.width("full").align_items("center").justify_content("between")
with ui.row() as row:
row.tailwind.align_items("center")
self._elements[key] = {
"control": el.FInput(
key,
password=True if key == "root_password" else False,
autocomplete=self.keys[key]["options"] if key in self.keys and "options" in self.keys[key] else None,
on_change=lambda e, key=key: set_key(key, e.value),
),
"row": key_row,
}
self._elements[key]["control"].value = value
if key in self.keys:
with ui.button(icon="help"):
ui.tooltip(self.keys[key]["description"])
ui.button(icon="remove", on_click=lambda _, key=key: remove_key(key))
def remove_key(key): def remove_key(self, key: str):
self._scroll.remove(self._elements[key]["row"]) self._scroll.remove(self._elements[key]["row"])
del self._elements[key] del self._elements[key]
if key in storage.answer(self.answer)[self.type]:
del storage.answer(self.answer)[self.type][key]
def set_key(key, value: str): def set_key(self, key: str, value: str):
v: Any = None v: Any = ""
if len(value) > 0: if len(value) > 0:
if key in self.keys and "type" in self.keys[key]: if key in self.keys and "type" in self.keys[key]:
if self.keys[key]["type"] == "list": if self.keys[key]["type"] == "list":
v = value[1:-1].split(",") v = value[1:-1].split(",")
elif self.keys[key]["type"] == "int": elif self.keys[key]["type"] == "int":
v = int(value) v = int(value)
else:
v = value
else: else:
if len(value) > 2 and value.strip()[0] == "[" and value.strip()[-1] == "]": v = value
v = value[1:-1].split(",") else:
elif value.isnumeric(): if len(value) > 2 and value.strip()[0] == "[" and value.strip()[-1] == "]":
v = int(value) v = value[1:-1].split(",")
else: elif value.isnumeric():
v = value v = int(value)
if self.type not in storage.answer(self.answer):
storage.answer(self.answer)[self.type] = {}
storage.answer(self.answer)[self.type][key] = v
def key_changed(e):
if self.help is not None:
if e.value in self.keys:
self.help.text = self.keys[e.value]["description"]
else: else:
self.help.text = "NA" v = value
if self.type not in storage.answer(self.answer):
storage.answer(self.answer)[self.type] = {}
storage.answer(self.answer)[self.type][key] = v
keys_controls() def key_changed(self, value: str):
if self.help is not None:
if value in self.keys:
self.help.text = self.keys[value]["description"]
else:
self.help.text = "NA"
class Global(Setting): class Global(Setting):
@ -163,3 +168,14 @@ class Disk(Setting):
"btrfs.hdsize": {"description": ""}, "btrfs.hdsize": {"description": ""},
} }
super().__init__(answer, type="disk-setup", keys=keys) super().__init__(answer, type="disk-setup", keys=keys)
def key_valid(self, key: str) -> bool:
if super().key_valid(key) is True:
if "filter" in key and "disk_list" in self._elements.keys():
el.Notification(f"Can not add {key} when disk_list is utilized!", type="negative", timeout=5)
return False
elif key == "disk_list" and any("filter" in k for k in self._elements.keys()):
el.Notification("Can not add disk_list when a filter is utilized!", type="negative", timeout=5)
return False
return True
return False

View File

@ -48,13 +48,11 @@ class System(Tab):
with self.scroll: with self.scroll:
with ui.row() as row: with ui.row() as row:
row.tailwind.width("full").align_items("center").justify_content("between") row.tailwind.width("full").align_items("center").justify_content("between")
with ui.row() as row: self._elements[restriction] = {
row.tailwind.align_items("center") "control": el.FInput(value=restriction, read_only=True),
self._elements[restriction] = { "row": row,
"control": el.FInput(value=restriction, read_only=True), }
"row": row, self._elements[restriction]["control"].tailwind.width("[420px]")
}
self._elements[restriction]["control"].tailwind.width("[420px]")
ui.button(icon="remove", on_click=lambda _, r=restriction: remove_restriction(r)) ui.button(icon="remove", on_click=lambda _, r=restriction: remove_restriction(r))
if self.type not in storage.answer(self.answer): if self.type not in storage.answer(self.answer):
storage.answer(self.answer)[self.type] = [] storage.answer(self.answer)[self.type] = []

View File

@ -102,6 +102,12 @@ async def post_answer(request: Request) -> PlainTextResponse:
if "network" in default_data and "network" in answer_data: if "network" in default_data and "network" in answer_data:
default_data["network"].update(answer_data["network"]) default_data["network"].update(answer_data["network"])
if "disk-setup" in default_data and "disk-setup" in answer_data: if "disk-setup" in default_data and "disk-setup" in answer_data:
if any("filter" in k for k in answer_data["disk-setup"]):
del default_data["disk-setup"]["disk_list"]
if "disk_list" in answer_data["disk-setup"]:
for key in list(default_data["disk-setup"].keys()):
if "filter" in key:
del default_data["disk-setup"][key]
default_data["disk-setup"].update(answer_data["disk-setup"]) default_data["disk-setup"].update(answer_data["disk-setup"])
return response(answer, system_info, default_data) return response(answer, system_info, default_data)
return response("Default", system_info, default_data) return response("Default", system_info, default_data)