diff --git a/bale/interfaces/sshdl.py b/bale/interfaces/sshdl.py index bda9b5c..4baf665 100644 --- a/bale/interfaces/sshdl.py +++ b/bale/interfaces/sshdl.py @@ -3,7 +3,7 @@ from pathlib import Path import stat from datetime import datetime import uuid -from nicegui import app, events, ui +from nicegui import app, background_tasks, events, ui from fastapi.responses import StreamingResponse import asyncssh from bale import elements as el @@ -87,7 +87,7 @@ class SshFileBrowse(ui.dialog): row.tailwind.height("[40px]") el.DButton("Download", on_click=self._start_download) ui.button("Exit", on_click=lambda: self.submit("exit")) - await self._update_grid() + await self._update_handler() async def _connect(self) -> Tuple[asyncssh.SSHClientConnection, asyncssh.SFTPClient]: ssh = await asyncssh.connect(self._zfs.hostname, username=self._zfs.username, client_keys=[self._zfs.key_path]) @@ -137,7 +137,7 @@ class SshFileBrowse(ui.dialog): "permissions": attributes.permissions, } - async def _update_grid(self) -> None: + async def _update_handler(self) -> None: self._grid.call_api_method("showLoadingOverlay") if self._ssh is None or self._sftp is None: self._ssh, self._sftp = await self._connect() @@ -165,7 +165,7 @@ class SshFileBrowse(ui.dialog): async def _handle_double_click(self, e: events.GenericEventArguments) -> None: self.path = e.args["data"]["path"] if e.args["data"]["type"] == "directory": - await self._update_grid() + await self._update_handler() else: await self._start_download(e) @@ -226,8 +226,10 @@ 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._pattern = el.DInput("Pattern", on_change=self._update_grid) + self._filesystem = el.DSelect(list(filesystems.data.keys()), label="filesystem", with_input=True, on_change=self._update_handler) + with el.WRow(): + self._pattern = ui.input("Pattern").classes("col").on("keydown.enter", handler=self._update_handler) + el.LgButton(icon="search", on_click=self._update_handler) self._grid = ui.aggrid( { "defaultColDef": {"flex": 1, "sortable": True, "suppressMovable": True, "sortingOrder": ["asc", "desc"]}, @@ -264,15 +266,21 @@ class SshFileFind(SshFileBrowse): row.tailwind.height("[40px]") el.DButton("Download", on_click=self._start_download) ui.button("Exit", on_click=lambda: self.submit("exit")) - await self._update_grid() + self._grid.call_api_method("hideOverlay") - async def _update_grid(self) -> None: - self._grid.call_api_method("showLoadingOverlay") - if self._filesystem is not None: + async def _update_handler(self) -> None: + if len(self._pattern.value) > 0 and self._filesystem is not None: + self._grid.call_api_method("showLoadingOverlay") + self._filesystem.props("readonly") + self._pattern.props("readonly") files = await self._zfs.find_files_in_snapshots(filesystem=self._filesystem.value, pattern=self._pattern.value) self._grid.options["rowData"] = files.data - self._grid.update() - self._grid.call_api_method("hideOverlay") + if files.truncated is True: + el.notify("Too many files found, truncating list.", type="warning") + self._grid.update() + self._filesystem.props(remove="readonly") + self._pattern.props(remove="readonly") + self._grid.call_api_method("hideOverlay") async def _handle_double_click(self, e: events.GenericEventArguments) -> None: await self._start_download(e) diff --git a/bale/interfaces/zfs.py b/bale/interfaces/zfs.py index 32c412e..813cdc5 100644 --- a/bale/interfaces/zfs.py +++ b/bale/interfaces/zfs.py @@ -166,27 +166,28 @@ class Zfs: return result async def find_files_in_snapshots(self, filesystem: str, pattern: str) -> Result: - filesystems = await self.filesystems - if filesystem in filesystems.data.keys(): - if "mountpoint" in filesystems.data[filesystem]: - command = f"find {filesystems.data[filesystem]['mountpoint']}/.zfs/snapshot -type f -name '{pattern}' -printf '%h\t%f\t%s\t%T@\n'" - result = await self.execute(command=command, notify=False) - files = [] - for line in result.stdout_lines: - matches = re.match( - "^(?P[^\t]+)\t(?P[^\t]+)\t(?P[^\t]+)\t(?P[^\n]+)", - line, - ) - if matches is not None: - md = matches.groupdict() - md["path"] = f"{md['location']}/{md['name']}" - 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 + try: + filesystems = await self.filesystems + command = f"find {filesystems.data[filesystem]['mountpoint']}/.zfs/snapshot -type f -name '{pattern}' -printf '%h\t%f\t%s\t%T@\n'" + result = await self.execute(command=command, notify=False, max_output_lines=1000) + files = [] + for line in result.stdout_lines: + matches = re.match( + "^(?P[^\t]+)\t(?P[^\t]+)\t(?P[^\t]+)\t(?P[^\n]+)", + line, + ) + if matches is not None: + md = matches.groupdict() + md["path"] = f"{md['location']}/{md['name']}" + 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 + except KeyError: + pass return Result() @property diff --git a/bale/tabs/manage.py b/bale/tabs/manage.py index 56b62ae..2be8ad8 100644 --- a/bale/tabs/manage.py +++ b/bale/tabs/manage.py @@ -111,9 +111,12 @@ class Manage(Tab): result = await SelectionConfirm(container=self._confirm, label=">BROWSE<") if result == "confirm": rows = await self._grid.get_selected_rows() - filesystems = await self.zfs.filesystems - mount_path = filesystems.data[rows[0]["filesystem"]]["mountpoint"] - await sshdl.SshFileBrowse(zfs=self.zfs, path=f"{mount_path}/.zfs/snapshot/{rows[0]['name']}") + try: + filesystems = await self.zfs.filesystems + mount_path = filesystems.data[rows[0]["filesystem"]]["mountpoint"] + await sshdl.SshFileBrowse(zfs=self.zfs, path=f"{mount_path}/.zfs/snapshot/{rows[0]['name']}") + except KeyError: + el.notify(f"Unable to browse {rows[0]['filesystem']}", type="warning") self._set_selection() async def _find(self) -> None: