5 Commits

Author SHA1 Message Date
Natan Keddem d322612fc8 refactor and fix table display and sorting 2023-11-17 23:18:03 -05:00
Natan Keddem 3d13876804 optimize startup 2023-11-17 23:16:15 -05:00
Natan Keddem 4685939cae fixed arbitrary target path recall 2023-11-16 21:37:21 -05:00
Natan Keddem dfcafed973 refactor startup builders 2023-11-16 19:34:52 -05:00
Natan Keddem 566fb9442c added arbitrary target path selection 2023-11-16 19:05:53 -05:00
8 changed files with 62 additions and 40 deletions
+3 -6
View File
@@ -1,3 +1,4 @@
import asyncio
from nicegui import ui
from bale import elements as el
import bale.logo as logo
@@ -26,7 +27,7 @@ class Content:
self._automation = None
self._history = None
def build(self):
async def build(self):
self._header = ui.header(bordered=True).classes("bg-dark q-pt-sm q-pb-xs")
self._header.tailwind.border_color(f"[{el.orange}]").min_width("[920px]")
self._header.visible = False
@@ -44,11 +45,7 @@ class Content:
self._tab_panels = (
ui.tab_panels(self._tabs, value="Manage", on_change=lambda e: self._tab_changed(e), animated=False).classes("w-full h-full").bind_visibility_from(self._header)
)
ui.timer(1, self.select_default, once=True)
async def select_default(self):
tab = Tab(spinner=None)
default = tab.common.get("default", "")
default = Tab(spinner=None).common.get("default", "")
if default != "":
await self.host_selected(default)
+10 -8
View File
@@ -162,16 +162,17 @@ class FInput(ui.input):
class DSelect(ui.select):
def __init__(
self,
options: List | Dict,
options: Union[List, Dict],
*,
label: str | None = None,
label: Optional[str] = None,
value: Any = None,
on_change: Callable[..., Any] | None = None,
on_change: Optional[Callable[..., Any]] = None,
with_input: bool = False,
new_value_mode: Optional[Literal["add", "add-unique", "toggle"]] = None,
multiple: bool = False,
clearable: bool = False,
) -> None:
super().__init__(options, label=label, value=value, on_change=on_change, with_input=with_input, multiple=multiple, clearable=clearable)
super().__init__(options, label=label, value=value, on_change=on_change, with_input=with_input, new_value_mode=new_value_mode, multiple=multiple, clearable=clearable)
self.tailwind.width("full")
if multiple is True:
self.props("use-chips")
@@ -180,16 +181,17 @@ class DSelect(ui.select):
class FSelect(ui.select):
def __init__(
self,
options: List | Dict,
options: Union[List, Dict],
*,
label: str | None = None,
label: Optional[str] = None,
value: Any = None,
on_change: Callable[..., Any] | None = None,
on_change: Optional[Callable[..., Any]] = None,
with_input: bool = False,
new_value_mode: Optional[Literal["add", "add-unique", "toggle"]] = None,
multiple: bool = False,
clearable: bool = False,
) -> None:
super().__init__(options, label=label, value=value, on_change=on_change, with_input=with_input, multiple=multiple, clearable=clearable)
super().__init__(options, label=label, value=value, on_change=on_change, with_input=with_input, new_value_mode=new_value_mode, multiple=multiple, clearable=clearable)
self.tailwind.width("64")
+7 -7
View File
@@ -226,9 +226,7 @@ class SshFileFind(SshFileBrowse):
with el.DBody(height="fit", width="[90vw]"):
with el.WColumn().classes("col"):
filesystems = await self._zfs.filesystems
self._filesystem = el.DSelect(
list(filesystems.data.keys()), label="filesystem", with_input=True, on_change=self._update_grid
)
self._filesystem = el.DSelect(list(filesystems.data.keys()), label="filesystem", with_input=True, on_change=self._update_grid)
self._pattern = el.DInput("Pattern", on_change=self._update_grid)
self._grid = ui.aggrid(
{
@@ -237,12 +235,14 @@ class SshFileFind(SshFileBrowse):
{"field": "name", "headerName": "Name", "flex": 1, "sort": "desc", "resizable": True},
{"field": "location", "headerName": "Location", "flex": 1, "resizable": True},
{
"field": "modified_datetime",
"headerName": "Modified",
"field": "modified_timestamp",
"filter": "agTextColumnFilter",
"maxWidth": 200,
":comparator": """(valueA, valueB, nodeA, nodeB, isInverted) => {
return (nodeA.data.modified_timestamp > nodeB.data.modified_timestamp) ? -1 : 1;
}""",
":cellRenderer": """(data) => {
var date = new Date(data.value * 1000).toLocaleString(undefined, {dateStyle: 'short', timeStyle: 'short', hour12: false});;
return date;
}""",
},
{
"field": "size",
+8 -4
View File
@@ -180,8 +180,10 @@ class Zfs:
if matches is not None:
md = matches.groupdict()
md["path"] = f"{md['location']}/{md['name']}"
md["size"] = format_bytes(int(md["bytes"]))
md["bytes"] = int(md["bytes"])
md["size"] = format_bytes(md["bytes"])
md["modified_datetime"] = datetime.fromtimestamp(float(md["modified_timestamp"])).strftime("%Y/%m/%d %H:%M:%S")
md["modified_timestamp"] = float(md["modified_timestamp"])
files.append(md)
result.data = files
return result
@@ -219,9 +221,11 @@ class Zfs:
matches = re.match("^(?P<filesystem>[^@]+)@(?P<name>[^\t]+)\t(?P<used_bytes>[^\t]+)\t(?P<creation>[^\t]+)\t(?P<userrefs>[^\n]+)", line)
if matches is not None:
md = matches.groupdict()
md["creation_date"] = datetime.fromtimestamp(int(md["creation"])).strftime("%Y/%m/%d")
md["creation_time"] = datetime.fromtimestamp(int(md["creation"])).strftime("%H:%M")
md["used"] = format_bytes(int(md["used_bytes"]))
md["used_bytes"] = int(md["used_bytes"])
md["creation"] = int(md["creation"])
md["creation_date"] = datetime.fromtimestamp(md["creation"]).strftime("%Y/%m/%d")
md["creation_time"] = datetime.fromtimestamp(md["creation"]).strftime("%H:%M")
md["used"] = format_bytes(md["used_bytes"])
snapshot = f"{md['filesystem']}@{md['name']}"
snapshots[snapshot] = md
self._last_data[query] = snapshots
+5 -4
View File
@@ -1,4 +1,5 @@
from nicegui import app, ui
import asyncio
from nicegui import app, Client, ui
from bale import elements as el
from bale.drawer import Drawer
from bale.content import Content
@@ -9,8 +10,8 @@ logger = logging.getLogger(__name__)
def build():
@ui.page("/")
def page() -> None:
@ui.page("/", response_timeout=30)
async def index(client: Client) -> None:
app.add_static_files("/static", "static")
el.load_element_css()
cli.load_terminal_css()
@@ -28,4 +29,4 @@ def build():
content = Content()
drawer = Drawer(column, content.host_selected, content.hide)
drawer.build()
content.build()
await content.build()
+4 -2
View File
@@ -491,10 +491,10 @@ class Automation(Tab):
row.tailwind.width("[860px]").justify_content("center")
with ui.column() as col:
col.tailwind.height("full").width("[420px]")
self.hosts = el.DSelect(source_hosts, label="Source Host(s)", multiple=True, with_input=True)
self.target_host = el.DSelect(target_host, label="Target Host", on_change=target_host_selected)
self.target_paths = [""]
self.target_path = el.DSelect(self.target_paths, value="", label="Target Path", on_change=target_path_selected)
self.target_path = el.DSelect(self.target_paths, value="", label="Target Path", new_value_mode="add-unique", on_change=target_path_selected)
self.hosts = el.DSelect(source_hosts, label="Source Host(s)", multiple=True, with_input=True)
all_fs_to_lists()
with ui.scroll_area().classes("col"):
self.parentchildren = el.DSelect(
@@ -535,6 +535,8 @@ class Automation(Tab):
while target_path not in self.target_path.options and tries < 20:
await asyncio.sleep(0.1)
tries = tries + 1
if target_path not in self.target_paths:
self.target_paths.append(target_path)
self.target_path.value = target_path
self.parentchildren.value = self.fs["values"].get("parentchildren", None)
self.parent.value = self.fs["values"].get("parent", None)
+10 -2
View File
@@ -47,8 +47,16 @@ class History(Tab):
"filter": "agTextColumnFilter",
"flex": 1,
},
{"headerName": "Date", "field": "date", "filter": "agDateColumnFilter", "maxWidth": 100},
{"headerName": "Time", "field": "time", "maxWidth": 100},
{
"headerName": "Timestamp",
"field": "timestamp",
"filter": "agTextColumnFilter",
"maxWidth": 200,
":cellRenderer": """(data) => {
var date = new Date(data.value * 1000).toLocaleString(undefined, {dateStyle: 'short', timeStyle: 'short', hour12: false});;
return date;
}""",
},
{
"headerName": "Status",
"field": "status",
+15 -7
View File
@@ -1,6 +1,6 @@
import asyncio
from copy import deepcopy
from nicegui import ui
from nicegui import background_tasks, ui
from . import SelectionConfirm, Tab, Task
from bale.result import Result
from bale import elements as el
@@ -75,11 +75,19 @@ class Manage(Tab):
"field": "used",
"maxWidth": 100,
":comparator": """(valueA, valueB, nodeA, nodeB, isInverted) => {
return (nodeA.data.used_bytes > nodeB.data.used_bytes) ? -1 : 1;
}""",
return (nodeA.data.used_bytes > nodeB.data.used_bytes) ? -1 : 1;
}""",
},
{
"headerName": "Created",
"field": "creation",
"filter": "agTextColumnFilter",
"maxWidth": 200,
":cellRenderer": """(data) => {
var date = new Date(data.value * 1000).toLocaleString(undefined, {dateStyle: 'short', timeStyle: 'short', hour12: false});;
return date;
}""",
},
{"headerName": "Creation Date", "field": "creation_date", "filter": "agDateColumnFilter", "maxWidth": 150},
{"headerName": "Creation Time", "field": "creation_time", "maxWidth": 150},
{"headerName": "Holds", "field": "userrefs", "filter": "agNumberColumnFilter", "maxWidth": 100},
],
"rowData": [],
@@ -92,8 +100,8 @@ class Manage(Tab):
self._spinner.visible = True
self.zfs.invalidate_query()
snapshots = await self.zfs.snapshots
await self.zfs.filesystems
await self.zfs.holds_for_snapshot()
background_tasks.create(self.zfs.filesystems, name="zfs_filesystems")
background_tasks.create(self.zfs.holds_for_snapshot(), name="zfs_holds")
self._grid.options["rowData"] = list(snapshots.data.values())
self._grid.update()
self._spinner.visible = False