@@ -1923,15 +1923,15 @@ def change_storage_class(self, bucket_name, prefix, new_storage_class, current_s
19231923 # Validate that we're not moving FROM Glacier tiers
19241924 glacier_tiers = ['GLACIER' , 'DEEP_ARCHIVE' ]
19251925 if current_storage_class in glacier_tiers :
1926- log (f'\n [red]Error : Cannot change storage class from { current_storage_class } [/red] ' )
1926+ log (f'\n Error : Cannot change storage class from { current_storage_class } ' )
19271927 log ('Moving data FROM Glacier or Deep Archive is not allowed.' )
19281928 log ('Please restore the data first if you need to change its storage tier.\n ' )
19291929 return False , 0 , 0 , 0 , 0
19301930
19311931 # Files that should remain in STANDARD tier
19321932 standard_files = ['Froster.allfiles.csv' , '.froster.md5sum' , '.froster-restored.md5sum' ]
19331933
1934- log (f'\n [bold]CHANGING STORAGE TIER[/bold] ' )
1934+ log (f'\n CHANGING STORAGE TIER' )
19351935 log (f' Bucket: { bucket_name } ' )
19361936 log (f' Prefix: { prefix } ' )
19371937 log (f' From: { current_storage_class } ' )
@@ -1982,10 +1982,10 @@ def change_storage_class(self, bucket_name, prefix, new_storage_class, current_s
19821982 log (f' Changed: { key } ' )
19831983
19841984 except Exception as e :
1985- log (f' [yellow] Warning: Failed to change storage class for { key } : { e } [/yellow] ' )
1985+ log (f' Warning: Failed to change storage class for { key } : { e } ' )
19861986 skipped_objects += 1
19871987
1988- log (f'\n [green] Storage class change completed[/green] ' )
1988+ log (f'\n Storage class change completed' )
19891989 log (f' Total objects: { total_objects } ' )
19901990 log (f' Changed: { changed_objects } ' )
19911991 log (f' Skipped: { skipped_objects } ' )
@@ -6060,6 +6060,97 @@ async def load_data(self, searchstr):
60606060 return
60616061
60626062
6063+ class ScreenConfirmTierChange (ModalScreen [bool ]):
6064+ """Modal confirmation dialog for storage tier changes"""
6065+
6066+ DEFAULT_CSS = """
6067+ ScreenConfirmTierChange {
6068+ align: center middle;
6069+ }
6070+
6071+ ScreenConfirmTierChange > Vertical {
6072+ background: $secondary;
6073+ width: auto;
6074+ height: auto;
6075+ border: thick $primary;
6076+ padding: 2 4;
6077+ }
6078+
6079+ ScreenConfirmTierChange > Vertical > * {
6080+ width: auto;
6081+ height: auto;
6082+ }
6083+
6084+ ScreenConfirmTierChange > Vertical > Label {
6085+ padding-bottom: 1;
6086+ }
6087+
6088+ ScreenConfirmTierChange > Vertical > Horizontal {
6089+ align: center middle;
6090+ padding-top: 2;
6091+ }
6092+
6093+ ScreenConfirmTierChange Button {
6094+ margin: 0 2;
6095+ }
6096+ """
6097+
6098+ def __init__ (self , folder , current_tier , new_tier , object_count , total_size_gib ):
6099+ super ().__init__ ()
6100+ self .folder = folder
6101+ self .current_tier = current_tier
6102+ self .new_tier = new_tier
6103+ self .object_count = object_count
6104+ self .total_size_gib = total_size_gib
6105+
6106+ def compose (self ) -> ComposeResult :
6107+ with Vertical ():
6108+ yield Label ("[bold]Confirm Storage Tier Change[/bold]" )
6109+ yield Label ("" )
6110+ yield Label (f"Folder: { self .folder } " )
6111+ yield Label (f"Current tier: { self .current_tier } " )
6112+ yield Label (f"New tier: { self .new_tier } " )
6113+ yield Label (f"Objects to change: { self .object_count } " )
6114+ yield Label (f"Total size: { self .total_size_gib :.2f} GiB" )
6115+ yield Label ("" )
6116+ yield Label ("[yellow]This operation will change the storage class of all objects[/yellow]" )
6117+ yield Label ("[yellow](except metadata files which remain in STANDARD)[/yellow]" )
6118+
6119+ with Horizontal ():
6120+ yield Button ("Proceed" , id = "yes" , variant = "primary" )
6121+ yield Button ("Cancel" , id = "no" , variant = "default" )
6122+
6123+ def on_button_pressed (self , event : Button .Pressed ) -> None :
6124+ self .dismiss (result = event .button .id == "yes" )
6125+
6126+
6127+ class TableStorageTierConfirmApp (App [bool ]):
6128+ """Wrapper app to show the confirmation modal"""
6129+
6130+ def __init__ (self , folder , current_tier , new_tier , object_count , total_size_gib ):
6131+ super ().__init__ ()
6132+ self .folder = folder
6133+ self .current_tier = current_tier
6134+ self .new_tier = new_tier
6135+ self .object_count = object_count
6136+ self .total_size_gib = total_size_gib
6137+
6138+ def on_mount (self ) -> None :
6139+ self .push_screen (
6140+ ScreenConfirmTierChange (
6141+ self .folder ,
6142+ self .current_tier ,
6143+ self .new_tier ,
6144+ self .object_count ,
6145+ self .total_size_gib
6146+ ),
6147+ self .handle_result
6148+ )
6149+
6150+ def handle_result (self , result : bool ) -> None :
6151+ self .exit (result )
6152+
6153+
60636154class TableStorageTierSelector (App [str ]):
60646155 """Interactive TUI for selecting AWS S3 storage tier with cost information"""
60656156
@@ -7318,7 +7409,7 @@ def _change_storage_tier(self, arch: Archiver, aws: AWSBoto, folders):
73187409 archive_info = arch .froster_archives_get_entry (folder )
73197410
73207411 if not archive_info :
7321- log (f'\n [red]Error : Folder { folder } is not archived[/red] \n ' )
7412+ log (f'\n Error : Folder { folder } is not archived\n ' )
73227413 return False
73237414
73247415 current_tier = archive_info ['s3_storage_class' ]
@@ -7327,7 +7418,7 @@ def _change_storage_tier(self, arch: Archiver, aws: AWSBoto, folders):
73277418
73287419 # Check if folder is in Glacier/Deep Archive
73297420 if current_tier in ['GLACIER' , 'DEEP_ARCHIVE' ]:
7330- log (f'\n [red]Error : Cannot change storage tier from { current_tier } [/red] ' )
7421+ log (f'\n Error : Cannot change storage tier from { current_tier } ' )
73317422 log ('Moving data FROM Glacier or Deep Archive is not allowed.' )
73327423 log ('Please restore the data first if you need to change its storage tier.\n ' )
73337424 return False
@@ -7368,20 +7459,22 @@ def _change_storage_tier(self, arch: Archiver, aws: AWSBoto, folders):
73687459 new_tier = app .run ()
73697460
73707461 if not new_tier :
7371- log ('\n [yellow]Storage tier change cancelled[/yellow] \n ' )
7462+ log ('\n Storage tier change cancelled\n ' )
73727463 return False
73737464
7374- # Confirm the change
7375- log (f'\n [bold]Confirm Storage Tier Change[/bold]' )
7376- log (f' Folder: { folder } ' )
7377- log (f' Current tier: { current_tier } ' )
7378- log (f' New tier: { new_tier } ' )
7379- log (f' Objects to change: { object_count } ' )
7380- log (f' Total size: { total_size / (1024 ** 3 ):.2f} GiB\n ' )
7465+ # Show confirmation modal
7466+ confirm_app = TableStorageTierConfirmApp (
7467+ folder = folder ,
7468+ current_tier = current_tier ,
7469+ new_tier = new_tier ,
7470+ object_count = object_count ,
7471+ total_size_gib = total_size / (1024 ** 3 )
7472+ )
7473+
7474+ confirmed = confirm_app .run ()
73817475
7382- response = input ('Proceed with storage tier change? (yes/no): ' )
7383- if response .lower () not in ['yes' , 'y' ]:
7384- log ('\n [yellow]Storage tier change cancelled[/yellow]\n ' )
7476+ if not confirmed :
7477+ log ('\n Storage tier change cancelled\n ' )
73857478 return False
73867479
73877480 # Perform the storage class change
@@ -7393,14 +7486,14 @@ def _change_storage_tier(self, arch: Archiver, aws: AWSBoto, folders):
73937486 )
73947487
73957488 if not success :
7396- log ('\n [red]Storage tier change failed[/red] \n ' )
7489+ log ('\n Storage tier change failed\n ' )
73977490 return False
73987491
73997492 # Update the local database
74007493 archive_info ['s3_storage_class' ] = new_tier
74017494 arch ._archive_json_add_entry (key = folder .rstrip (os .path .sep ), value = archive_info )
74027495
7403- log (f'\n [green]Successfully changed storage tier[/green] ' )
7496+ log (f'\n Successfully changed storage tier' )
74047497 log (f' Database updated: { arch .archive_json } \n ' )
74057498
74067499 return True
0 commit comments