1- from typing import Any , Dict , List , Union
1+ from typing import Any , Callable , Dict , List , Union
22import asyncio
33from datetime import datetime
44import json
@@ -36,20 +36,20 @@ def populate_job_handler(app: str, job_id: str, host: str):
3636 return job_handlers [job_id ]
3737
3838
39- class CommandTemplate (string .Template ):
39+ class AutomationTemplate (string .Template ):
4040 delimiter = ""
4141
4242
4343async def automation_job (** kwargs ) -> None :
4444 if "data" in kwargs :
4545 jd = json .loads (kwargs ["data" ])
46- command = CommandTemplate (jd ["command" ])
46+ command = AutomationTemplate (jd ["command" ])
4747 tab = Tab (host = None , spinner = None )
4848 if jd ["app" ] == "zfs_autobackup" :
4949 d = scheduler .Zfs_Autobackup (** jd )
5050 populate_job_handler (app = d .app , job_id = d .id , host = d .host )
5151 if job_handlers [d .id ].is_busy is False :
52- result = await job_handlers [d .id ].execute (command .safe_substitute (host = d .host ))
52+ result = await job_handlers [d .id ].execute (command .safe_substitute (name = d . name , host = d .host ))
5353 result .name = d .host
5454 result .status = "success" if result .return_code == 0 else "error"
5555 if d .pipe_success is True and result .status == "success" :
@@ -63,7 +63,7 @@ async def automation_job(**kwargs) -> None:
6363 d = scheduler .Automation (** jd )
6464 populate_job_handler (app = d .app , job_id = d .id , host = d .host )
6565 if job_handlers [d .id ].is_busy is False :
66- result = await job_handlers [d .id ].execute (command .safe_substitute (host = d .host ))
66+ result = await job_handlers [d .id ].execute (command .safe_substitute (name = d . name , host = d .host ))
6767 result .name = d .host
6868 if d .pipe_success is True and result .status == "success" :
6969 tab .pipe_result (result = result )
@@ -76,7 +76,7 @@ async def automation_job(**kwargs) -> None:
7676 d = scheduler .Automation (** jd )
7777 populate_job_handler (app = d .app , job_id = d .id , host = d .host )
7878 if job_handlers [d .id ].is_busy is False :
79- result = await job_handlers [d .id ].execute (command .safe_substitute (host = d .host ))
79+ result = await job_handlers [d .id ].execute (command .safe_substitute (name = d . name , host = d .host ))
8080 result .name = d .host
8181 if d .pipe_success is True and result .status == "success" :
8282 tab .pipe_result (result = result )
@@ -99,7 +99,7 @@ def __init__(self, spinner, host: Union[str, None] = None) -> None:
9999 self .job_data : Dict [str , str ] = {}
100100 self .job_names : List [str ] = []
101101 self .default_options : Dict [str , str ] = {}
102- self .build_command : str = ""
102+ self .build_command : Callable
103103 self .target_host : el .DSelect
104104 self .target_paths : List [str ] = ["" ]
105105 self .target_path : el .DSelect
@@ -308,26 +308,17 @@ async def _edit_automation(self) -> None:
308308 await self ._create_automation (rows [0 ]["name" ])
309309 self ._set_selection ()
310310
311- async def _add_prop_to_fs (
312- self ,
313- host : str ,
314- prop : str ,
315- value : str ,
316- module : str = "autobackup" ,
317- filesystems : Union [List [str ], None ] = None ,
318- ) -> None :
311+ async def _add_prop_to_fs (self , host : str , prop : str , value : str , filesystems : Union [List [str ], None ] = None ) -> None :
319312 if filesystems is not None :
320- full_prop = f"{ module } :{ prop } "
321313 for fs in filesystems :
322- result = await self ._zfs [host ].add_filesystem_prop (filesystem = fs , prop = full_prop , value = value )
314+ result = await self ._zfs [host ].add_filesystem_prop (filesystem = fs , prop = prop , value = value )
323315 self .add_history (result = result )
324316
325- async def _remove_prop_from_all_fs (self , host : str , prop : str , module : str = "autobackup" ) -> None :
326- full_prop = f"{ module } :{ prop } "
327- filesystems_with_prop_result = await self ._zfs [host ].filesystems_with_prop (full_prop )
317+ async def _remove_prop_from_all_fs (self , host : str , prop : str ) -> None :
318+ filesystems_with_prop_result = await self ._zfs [host ].filesystems_with_prop (prop )
328319 filesystems_with_prop = list (filesystems_with_prop_result .data )
329320 for fs in filesystems_with_prop :
330- result = await self ._zfs [host ].remove_filesystem_prop (filesystem = fs , prop = full_prop )
321+ result = await self ._zfs [host ].remove_filesystem_prop (filesystem = fs , prop = prop )
331322 self .add_history (result = result )
332323
333324 async def _create_automation (self , name : str = "" ) -> None :
@@ -421,15 +412,16 @@ async def target_host_selected() -> None:
421412 self .target_path .update ()
422413 self .target_path .value = ""
423414
424- async def target_path_selected () -> None :
425- self .build_command ()
426-
427415 def build_command () -> None :
416+ try :
417+ prop_suffix = self .prop .value .split (":" )[1 ]
418+ except IndexError :
419+ prop_suffix = ""
428420 base = ""
429421 for key , value in self .picked_options .items ():
430422 base = base + f" --{ key } { f' { value } ' if value != '' else '' } "
431423 target_path = f"{ f' { self .target_path .value } ' if self .target_path .value != '' else '' } "
432- base = base + f" { self . auto_name . value . lower () } " + target_path
424+ base = base + f" { prop_suffix } " + target_path
433425 self .command .value = base
434426
435427 def all_fs_to_lists ():
@@ -476,6 +468,17 @@ def cull_fs_list(e: events.GenericEventArguments, value: str = "false") -> None:
476468 self .children .update ()
477469 self .exclude .update ()
478470
471+ def validate_prop (value ):
472+ parts = value .split (":" )
473+ for part in parts :
474+ if part .find (" " ) != - 1 :
475+ return False
476+ if len (part ) < 1 :
477+ return False
478+ if len (parts ) != 2 :
479+ return False
480+ return True
481+
479482 if name == "" :
480483 self .default_options = {
481484 "verbose" : "" ,
@@ -496,9 +499,11 @@ def cull_fs_list(e: events.GenericEventArguments, value: str = "false") -> None:
496499 row .tailwind .width ("[860px]" ).justify_content ("center" )
497500 with ui .column () as col :
498501 col .tailwind .height ("full" ).width ("[420px]" )
502+ self .prop = el .DInput (label = "Property" , value = "autobackup:{name}" , on_change = build_command , validation = validate_prop )
503+ self .app_em .append (self .prop )
499504 self .target_host = el .DSelect (target_host , label = "Target Host" , on_change = target_host_selected )
500505 self .target_paths = ["" ]
501- self .target_path = el .DSelect (self .target_paths , value = "" , label = "Target Path" , new_value_mode = "add-unique" , on_change = target_path_selected )
506+ self .target_path = el .DSelect (self .target_paths , value = "" , label = "Target Path" , new_value_mode = "add-unique" , on_change = build_command )
502507 self .hosts = el .DSelect (source_hosts , label = "Source Host(s)" , multiple = True , with_input = True )
503508 all_fs_to_lists ()
504509 with ui .scroll_area ().classes ("col" ):
@@ -534,6 +539,7 @@ def cull_fs_list(e: events.GenericEventArguments, value: str = "false") -> None:
534539 col .tailwind .height ("full" ).width ("[420px]" )
535540 options_controls ()
536541 if name != "" :
542+ self .prop .value = self .job_data .get ("prop" , "autobackup:{name}" )
537543 self .target_host .value = self .job_data .get ("target_host" , "" )
538544 target_path = self .job_data .get ("target_path" , "" )
539545 tries = 0
@@ -758,7 +764,9 @@ def validate_hosts(e):
758764 with el .WRow () as row :
759765 row .tailwind .height ("[40px]" )
760766 self .as_spinner = el .Spinner ()
767+ self .app_em = el .ErrorAggregator ()
761768 self .save = el .DButton ("SAVE" , on_click = lambda : automation_dialog .submit ("save" ))
769+ self .save .bind_enabled_from (self .app_em , "no_errors" )
762770 el .Spinner (master = self .as_spinner )
763771 self .auto_name .value = name
764772 if name != "" :
@@ -779,18 +787,21 @@ def validate_hosts(e):
779787 self .scheduler .scheduler .remove_job (job .id )
780788 for host in hosts :
781789 auto_id = f"{ auto_name } @{ host } "
782- await self ._remove_prop_from_all_fs (host = host , prop = auto_name )
783- await self ._add_prop_to_fs (host = host , prop = auto_name , value = "true" , filesystems = self .parentchildren .value )
784- await self ._add_prop_to_fs (host = host , prop = auto_name , value = "parent" , filesystems = self .parent .value )
785- await self ._add_prop_to_fs (host = host , prop = auto_name , value = "child" , filesystems = self .children .value )
786- await self ._add_prop_to_fs (host = host , prop = auto_name , value = "false" , filesystems = self .exclude .value )
790+ command = AutomationTemplate (self .prop .value )
791+ prop = command .safe_substitute (name = auto_name , host = host )
792+ await self ._remove_prop_from_all_fs (host = host , prop = prop )
793+ await self ._add_prop_to_fs (host = host , prop = prop , value = "true" , filesystems = self .parentchildren .value )
794+ await self ._add_prop_to_fs (host = host , prop = prop , value = "parent" , filesystems = self .parent .value )
795+ await self ._add_prop_to_fs (host = host , prop = prop , value = "child" , filesystems = self .children .value )
796+ await self ._add_prop_to_fs (host = host , prop = prop , value = "false" , filesystems = self .exclude .value )
787797 self .fs ["values" ] = {}
788798 self .fs ["values" ]["parentchildren" ] = self .parentchildren .value
789799 self .fs ["values" ]["parent" ] = self .parent .value
790800 self .fs ["values" ]["children" ] = self .children .value
791801 self .fs ["values" ]["exclude" ] = self .exclude .value
792802 auto = scheduler .Zfs_Autobackup (
793803 id = auto_id ,
804+ name = auto_name ,
794805 hosts = hosts ,
795806 host = host ,
796807 command = "python -m zfs_autobackup.ZfsAutobackup" + self .command .value ,
@@ -803,6 +814,7 @@ def validate_hosts(e):
803814 filesystems = self .fs ,
804815 pipe_success = self .pipe_success .value ,
805816 pipe_error = self .pipe_error .value ,
817+ prop = self .prop .value ,
806818 )
807819 self .scheduler .scheduler .add_job (
808820 automation_job ,
@@ -822,6 +834,7 @@ def validate_hosts(e):
822834 auto_id = f"{ auto_name } @{ host } "
823835 auto = scheduler .Automation (
824836 id = auto_id ,
837+ name = auto_name ,
825838 app = self .app .value ,
826839 hosts = hosts ,
827840 host = host ,
@@ -844,6 +857,7 @@ def validate_hosts(e):
844857 auto_id = f"{ auto_name } @{ self .host } "
845858 auto = scheduler .Automation (
846859 id = auto_id ,
860+ name = auto_name ,
847861 app = self .app .value ,
848862 hosts = hosts ,
849863 host = self .host ,
0 commit comments