From fd9878b83693d45ab0efa2a7d885a8d0fdc79471 Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Wed, 3 Jul 2024 22:48:48 +0200 Subject: [PATCH 1/5] #14 Add first details for S3 buckets --- src/AWSManager.fbp | 1234 +++++++++++++++++++++++++++++++++++--------- src/aws_s3.py | 10 + src/awsmanager.py | 9 + src/gui.py | 99 +++- src/ui_aws_s3.py | 40 ++ 5 files changed, 1148 insertions(+), 244 deletions(-) diff --git a/src/AWSManager.fbp b/src/AWSManager.fbp index c3fd640..d45d43c 100644 --- a/src/AWSManager.fbp +++ b/src/AWSManager.fbp @@ -241,7 +241,7 @@ EC2 0 - + 1 1 1 @@ -293,16 +293,16 @@ wxTAB_TRAVERSAL - + bSizer8 wxVERTICAL none - + 5 wxEXPAND 1 - + 1 1 1 @@ -556,8 +556,8 @@ - - + + 1 1 1 @@ -609,7 +609,7 @@ wxTAB_TRAVERSAL - + bSizer13 wxVERTICAL @@ -4333,11 +4333,11 @@ - + S3 - 0 - + 1 + 1 1 1 @@ -4389,16 +4389,16 @@ wxTAB_TRAVERSAL - + bSizer81 wxVERTICAL none - + 5 wxEXPAND 1 - + 1 1 1 @@ -4571,6 +4571,7 @@ + aws_s3_load_details @@ -4651,8 +4652,8 @@ - - + + 1 1 1 @@ -4704,234 +4705,983 @@ wxTAB_TRAVERSAL - - - - - - - - - - RDS - 0 - - 1 - 1 - 1 - 1 - 0 - panelRDS - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - panelRDS - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxTAB_TRAVERSAL - - - bSizer82 - wxVERTICAL - none - - 5 - wxEXPAND - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - 0 - - 0 - - 1 - m_splitter12 - 1 - - - protected - 1 - - Resizable - 0.0 - 0 - -1 - 1 - - wxSPLIT_VERTICAL - wxSP_3D - ; ; forward_declare - 0 - - - - - - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - panelRDSTree - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxTAB_TRAVERSAL - + + 1 + wxBOTH + 0 + 1 + 0 - bSizer42 - wxVERTICAL + fgSizer15 + wxFLEX_GROWMODE_SPECIFIED none - + 3 + 0 + 5 - wxALL|wxEXPAND + wxEXPAND 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 + + 4 + wxBOTH + 1 + + 0 + + fgSizerS3Details + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Bucket Name + 0 + + 0 + + + 0 + + 1 + staticTextS3_Details_BucketName + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + textCtrlS3_Details_BucketName + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + Refresh + + 0 + + 0 + + + 0 + + 1 + buttonS3_Details_Refresh + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + aws_s3_refresh_bucket + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + Open in AWS + + 0 + + 0 + + + 0 + + 1 + buttonS3_Details_OpenMgmtConsole + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + aws_s3_open_mgmt_console + + + + + + 5 + wxEXPAND + 1 + + 2 + wxBOTH + 1 + 0 + 0 + + fgSizerS3Objects + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Objects + 0 + + 0 + + + 0 + + 1 + staticTextS3_Objects + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + treeCtrlS3_Objects + 1 + + + protected + 1 + + Resizable + 1 + + wxTR_DEFAULT_STYLE + ; ; forward_declare + 0 + + + + + + + + + + 5 + wxEXPAND + 1 + + 3 + wxBOTH + 1 + + 0 + + fgSizerS3Upload + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Upload file to S3 bucket + 0 + + 0 + + + 0 + + 1 + staticTextS3_Upload + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + comboBoxS3_Upload + 1 + + + protected + 1 + + Resizable + -1 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + Please select a key or enter a new key + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + Upload + + 0 + + 0 + + + 0 + + 1 + buttonS3_Upload + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + 1 + wxSYS_COLOUR_HIGHLIGHT + 1 + + 0 + 0 + wxID_ANY + Drag a file here to upload it to the S3 bucket + 0 + + 0 + + + 0 + + 1 + staticTextS3_Upload_DragZone + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + + + + + + + + + + + + RDS + 0 + + 1 + 1 + 1 + 1 + 0 + panelRDS + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + panelRDS + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer82 + wxVERTICAL + none + + 5 + wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + m_splitter12 + 1 + + + protected + 1 + + Resizable + 0.0 + 0 + -1 + 1 + + wxSPLIT_VERTICAL + wxSP_3D + ; ; forward_declare + 0 + + + + + + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + panelRDSTree + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer42 + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 treeRDS 1 @@ -5092,7 +5842,7 @@ Cloudfront - 1 + 0 1 1 @@ -5408,8 +6158,8 @@ - - + + 1 1 1 diff --git a/src/aws_s3.py b/src/aws_s3.py index 4ecfe53..639ae8a 100644 --- a/src/aws_s3.py +++ b/src/aws_s3.py @@ -9,3 +9,13 @@ def get_s3_buckets(region): for bucket in buckets['Buckets']: buckets_list.append(bucket) return buckets_list + + +# get all objects of a bucket +def get_s3_bucket_objects(region, bucket_name): + s3 = boto3.client('s3', region_name=region) + objects = s3.list_objects_v2(Bucket=bucket_name) + objects_list = [] + for obj in objects['Contents']: + objects_list.append(obj) + return objects_list diff --git a/src/awsmanager.py b/src/awsmanager.py index 2c72b34..5a4a2b6 100644 --- a/src/awsmanager.py +++ b/src/awsmanager.py @@ -107,9 +107,18 @@ def aws_lambda_invoke(self, event): def aws_lambda_open_mgmt_console(self, event): ui_aws_lambda.aws_lambda_open_mgmt_console(self, event) + def aws_s3_load_details(self, event): + ui_aws_s3.aws_s3_load_details(self, event) + def aws_s3_reload(self, event): ui_aws_s3.aws_s3_reload(self, event) + def aws_s3_refresh_bucket(self, event): + ui_aws_s3.aws_s3_refresh_bucket(self, event) + + def aws_s3_open_mgmt_console(self, event): + ui_aws_s3.aws_s3_open_mgmt_console(self, event) + def aws_rds_reload(self, event): ui_aws_rds.aws_rds_reload(self, event) diff --git a/src/gui.py b/src/gui.py index 38215a6..c3ffb7a 100644 --- a/src/gui.py +++ b/src/gui.py @@ -422,6 +422,89 @@ def __init__( self, parent ): self.panelS3Tree.Layout() bSizer41.Fit( self.panelS3Tree ) self.panelS3Details = wx.Panel( self.m_splitter11, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + fgSizer15 = wx.FlexGridSizer( 3, 1, 0, 0 ) + fgSizer15.AddGrowableCol( 0 ) + fgSizer15.AddGrowableRow( 1 ) + fgSizer15.SetFlexibleDirection( wx.BOTH ) + fgSizer15.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + fgSizerS3Details = wx.FlexGridSizer( 0, 4, 0, 0 ) + fgSizerS3Details.AddGrowableCol( 1 ) + fgSizerS3Details.SetFlexibleDirection( wx.BOTH ) + fgSizerS3Details.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + self.staticTextS3_Details_BucketName = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Bucket Name", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Details_BucketName.Wrap( -1 ) + + fgSizerS3Details.Add( self.staticTextS3_Details_BucketName, 0, wx.ALL, 5 ) + + self.textCtrlS3_Details_BucketName = wx.TextCtrl( self.panelS3Details, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizerS3Details.Add( self.textCtrlS3_Details_BucketName, 1, wx.ALL|wx.EXPAND, 5 ) + + self.buttonS3_Details_Refresh = wx.Button( self.panelS3Details, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizerS3Details.Add( self.buttonS3_Details_Refresh, 0, wx.ALL, 5 ) + + self.buttonS3_Details_OpenMgmtConsole = wx.Button( self.panelS3Details, wx.ID_ANY, u"Open in AWS", wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizerS3Details.Add( self.buttonS3_Details_OpenMgmtConsole, 0, wx.ALL, 5 ) + + + fgSizer15.Add( fgSizerS3Details, 1, wx.EXPAND, 5 ) + + fgSizerS3Objects = wx.FlexGridSizer( 0, 2, 0, 0 ) + fgSizerS3Objects.AddGrowableCol( 1 ) + fgSizerS3Objects.AddGrowableRow( 0 ) + fgSizerS3Objects.SetFlexibleDirection( wx.BOTH ) + fgSizerS3Objects.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + self.staticTextS3_Objects = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Objects", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Objects.Wrap( -1 ) + + fgSizerS3Objects.Add( self.staticTextS3_Objects, 0, wx.ALL, 5 ) + + self.treeCtrlS3_Objects = wx.TreeCtrl( self.panelS3Details, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE ) + fgSizerS3Objects.Add( self.treeCtrlS3_Objects, 1, wx.ALL|wx.EXPAND, 5 ) + + + fgSizer15.Add( fgSizerS3Objects, 1, wx.EXPAND, 5 ) + + fgSizerS3Upload = wx.FlexGridSizer( 0, 3, 0, 0 ) + fgSizerS3Upload.AddGrowableCol( 1 ) + fgSizerS3Upload.SetFlexibleDirection( wx.BOTH ) + fgSizerS3Upload.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + self.staticTextS3_Upload = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Upload file to S3 bucket", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Upload.Wrap( -1 ) + + fgSizerS3Upload.Add( self.staticTextS3_Upload, 0, wx.ALL, 5 ) + + comboBoxS3_UploadChoices = [] + self.comboBoxS3_Upload = wx.ComboBox( self.panelS3Details, wx.ID_ANY, u"Please select a key or enter a new key", wx.DefaultPosition, wx.DefaultSize, comboBoxS3_UploadChoices, 0 ) + fgSizerS3Upload.Add( self.comboBoxS3_Upload, 1, wx.ALL|wx.EXPAND, 5 ) + + self.buttonS3_Upload = wx.Button( self.panelS3Details, wx.ID_ANY, u"Upload", wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizerS3Upload.Add( self.buttonS3_Upload, 0, wx.ALL, 5 ) + + + fgSizerS3Upload.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.staticTextS3_Upload_DragZone = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Drag a file here to upload it to the S3 bucket", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Upload_DragZone.Wrap( -1 ) + + self.staticTextS3_Upload_DragZone.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) + self.staticTextS3_Upload_DragZone.DragAcceptFiles( True ) + + fgSizerS3Upload.Add( self.staticTextS3_Upload_DragZone, 1, wx.ALL|wx.EXPAND, 5 ) + + + fgSizerS3Upload.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + fgSizer15.Add( fgSizerS3Upload, 1, wx.EXPAND, 5 ) + + + self.panelS3Details.SetSizer( fgSizer15 ) + self.panelS3Details.Layout() + fgSizer15.Fit( self.panelS3Details ) self.m_splitter11.SplitVertically( self.panelS3Tree, self.panelS3Details, 0 ) bSizer81.Add( self.m_splitter11, 1, wx.EXPAND, 5 ) @@ -429,7 +512,7 @@ def __init__( self, parent ): self.panelS3.SetSizer( bSizer81 ) self.panelS3.Layout() bSizer81.Fit( self.panelS3 ) - self.m_auinotebook1.AddPage( self.panelS3, u"S3", False, wx.NullBitmap ) + self.m_auinotebook1.AddPage( self.panelS3, u"S3", True, wx.NullBitmap ) self.panelRDS = wx.Panel( self.m_auinotebook1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer82 = wx.BoxSizer( wx.VERTICAL ) @@ -576,7 +659,7 @@ def __init__( self, parent ): self.panelCloudfront.SetSizer( bSizer83 ) self.panelCloudfront.Layout() bSizer83.Fit( self.panelCloudfront ) - self.m_auinotebook1.AddPage( self.panelCloudfront, u"Cloudfront", True, wx.NullBitmap ) + self.m_auinotebook1.AddPage( self.panelCloudfront, u"Cloudfront", False, wx.NullBitmap ) gSizer1.Add( self.m_auinotebook1, 1, wx.EXPAND |wx.ALL, 5 ) @@ -604,7 +687,10 @@ def __init__( self, parent ): self.buttonLambda_RefreshFunction.Bind( wx.EVT_BUTTON, self.aws_lambda_refresh_function ) self.buttonLambda_OpenMgmtConsole.Bind( wx.EVT_BUTTON, self.aws_lambda_open_mgmt_console ) self.buttonLambda_Invoke.Bind( wx.EVT_BUTTON, self.aws_lambda_invoke ) + self.treeS3.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.aws_s3_load_details ) self.buttonReloadS3.Bind( wx.EVT_BUTTON, self.aws_s3_reload ) + self.buttonS3_Details_Refresh.Bind( wx.EVT_BUTTON, self.aws_s3_refresh_bucket ) + self.buttonS3_Details_OpenMgmtConsole.Bind( wx.EVT_BUTTON, self.aws_s3_open_mgmt_console ) self.buttonReloadRDS.Bind( wx.EVT_BUTTON, self.aws_rds_reload ) self.treeCloudfront.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.aws_cloudfront_load_details ) self.buttonReloadCloudfront.Bind( wx.EVT_BUTTON, self.aws_cloudfront_reload ) @@ -668,9 +754,18 @@ def aws_lambda_open_mgmt_console( self, event ): def aws_lambda_invoke( self, event ): event.Skip() + def aws_s3_load_details( self, event ): + event.Skip() + def aws_s3_reload( self, event ): event.Skip() + def aws_s3_refresh_bucket( self, event ): + event.Skip() + + def aws_s3_open_mgmt_console( self, event ): + event.Skip() + def aws_rds_reload( self, event ): event.Skip() diff --git a/src/ui_aws_s3.py b/src/ui_aws_s3.py index a17dd7e..1dedff0 100644 --- a/src/ui_aws_s3.py +++ b/src/ui_aws_s3.py @@ -4,6 +4,7 @@ import aws_s3 import settings import iconsaws +import webbrowser def aws_s3_reload(self, event): @@ -21,3 +22,42 @@ def aws_s3_reload(self, event): item = self.treeS3.AppendItem(root, bucket_name + ' (' + bucket_creation_date.strftime('%Y-%m-%d %H:%M:%S') + ')') self.treeS3.SetItemImage(item, s3_buckets, wx.TreeItemIcon_Normal) self.treeS3.ExpandAll() + + +def aws_s3_load_details(self, event): + # get the selected item + item = self.treeS3.GetSelection() + if not item: + return + # get the text of the selected item + text = self.treeS3.GetItemText(item) + # get the bucket name + bucket_name = text.split(' ')[0] + # load the objects of the bucket + objects = aws_s3.get_s3_bucket_objects(settings.read_config()['region'], bucket_name) + # clear the tree control + self.treeCtrlS3_Objects.DeleteAllItems() + root = self.treeCtrlS3_Objects.AddRoot('Objects') + for obj in objects: + object_name = obj['Key'] + object_size = obj['Size'] + object_last_modified = obj['LastModified'] + self.treeCtrlS3_Objects.AppendItem(root, object_name + ' (' + str(object_size) + ' bytes) - ' + object_last_modified.strftime('%Y-%m-%d %H:%M:%S')) + + self.treeCtrlS3_Objects.ExpandAll() + + +def aws_s3_refresh_bucket(self, event): + aws_s3_load_details(self, event) + + +def aws_s3_open_mgmt_console(self, event): + # get the selected item + item = self.treeS3.GetSelection() + if item: + # get the text of the selected item + text = self.treeS3.GetItemText(item) + # get the bucket name + bucket_name = text.split(' ')[0] + # open the management console + webbrowser.open('https://s3.console.aws.amazon.com/s3/buckets/' + bucket_name + '?region=' + settings.read_config()['region']) From 7c8fd35cbf1405f0dd32f03b84cfb0832f46c685 Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Thu, 4 Jul 2024 21:55:51 +0200 Subject: [PATCH 2/5] #14 Finalize upload feature --- src/AWSManager.fbp | 94 +++++++++++++++++++++++++++++++++++++++++++--- src/awsmanager.py | 9 +++++ src/gui.py | 28 ++++++++++++-- src/ui_aws_s3.py | 84 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 199 insertions(+), 16 deletions(-) diff --git a/src/AWSManager.fbp b/src/AWSManager.fbp index d45d43c..db93500 100644 --- a/src/AWSManager.fbp +++ b/src/AWSManager.fbp @@ -5147,6 +5147,7 @@ + aws_s3_selected_key @@ -5167,6 +5168,88 @@ none 0 0 + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + To upload a file, select a key to add the file into it. If you select a file object, the file will be overwritten. You can adjust the name, by just editing it. + 0 + + 0 + + + 0 + + 1 + staticTextS3_Upload_Details + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 400 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + 5 wxALL @@ -5233,7 +5316,7 @@ 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -5247,7 +5330,6 @@ 1 0 - 1 1 @@ -5266,11 +5348,12 @@ 0 + 0 0 1 - comboBoxS3_Upload + textCtrlS3_SelectedKey 1 @@ -5278,7 +5361,6 @@ 1 Resizable - -1 1 @@ -5289,7 +5371,7 @@ wxFILTER_NONE wxDefaultValidator - Please select a key or enter a new key + Select an object or key to upload a new file. @@ -5367,6 +5449,7 @@ + aws_s3_upload_file @@ -5439,6 +5522,7 @@ -1 + aws_s3_drop_file diff --git a/src/awsmanager.py b/src/awsmanager.py index 5a4a2b6..1848fa5 100644 --- a/src/awsmanager.py +++ b/src/awsmanager.py @@ -119,6 +119,15 @@ def aws_s3_refresh_bucket(self, event): def aws_s3_open_mgmt_console(self, event): ui_aws_s3.aws_s3_open_mgmt_console(self, event) + def aws_s3_selected_key(self, event): + ui_aws_s3.aws_s3_selected_key(self, event) + + def aws_s3_upload_file(self, event): + ui_aws_s3.aws_s3_upload_file(self, event) + + def aws_s3_drop_file(self, event): + ui_aws_s3.aws_s3_drop_file(self, event) + def aws_rds_reload(self, event): ui_aws_rds.aws_rds_reload(self, event) diff --git a/src/gui.py b/src/gui.py index c3ffb7a..435d98d 100644 --- a/src/gui.py +++ b/src/gui.py @@ -472,14 +472,24 @@ def __init__( self, parent ): fgSizerS3Upload.SetFlexibleDirection( wx.BOTH ) fgSizerS3Upload.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + fgSizerS3Upload.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.staticTextS3_Upload_Details = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"To upload a file, select a key to add the file into it. If you select a file object, the file will be overwritten. You can adjust the name, by just editing it.", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Upload_Details.Wrap( 400 ) + + fgSizerS3Upload.Add( self.staticTextS3_Upload_Details, 1, wx.ALL|wx.EXPAND, 5 ) + + + fgSizerS3Upload.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + self.staticTextS3_Upload = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Upload file to S3 bucket", wx.DefaultPosition, wx.DefaultSize, 0 ) self.staticTextS3_Upload.Wrap( -1 ) fgSizerS3Upload.Add( self.staticTextS3_Upload, 0, wx.ALL, 5 ) - comboBoxS3_UploadChoices = [] - self.comboBoxS3_Upload = wx.ComboBox( self.panelS3Details, wx.ID_ANY, u"Please select a key or enter a new key", wx.DefaultPosition, wx.DefaultSize, comboBoxS3_UploadChoices, 0 ) - fgSizerS3Upload.Add( self.comboBoxS3_Upload, 1, wx.ALL|wx.EXPAND, 5 ) + self.textCtrlS3_SelectedKey = wx.TextCtrl( self.panelS3Details, wx.ID_ANY, u"Select an object or key to upload a new file.", wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizerS3Upload.Add( self.textCtrlS3_SelectedKey, 1, wx.ALL|wx.EXPAND, 5 ) self.buttonS3_Upload = wx.Button( self.panelS3Details, wx.ID_ANY, u"Upload", wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizerS3Upload.Add( self.buttonS3_Upload, 0, wx.ALL, 5 ) @@ -691,6 +701,9 @@ def __init__( self, parent ): self.buttonReloadS3.Bind( wx.EVT_BUTTON, self.aws_s3_reload ) self.buttonS3_Details_Refresh.Bind( wx.EVT_BUTTON, self.aws_s3_refresh_bucket ) self.buttonS3_Details_OpenMgmtConsole.Bind( wx.EVT_BUTTON, self.aws_s3_open_mgmt_console ) + self.treeCtrlS3_Objects.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.aws_s3_selected_key ) + self.buttonS3_Upload.Bind( wx.EVT_BUTTON, self.aws_s3_upload_file ) + self.staticTextS3_Upload_DragZone.Bind( wx.EVT_DROP_FILES, self.aws_s3_drop_file ) self.buttonReloadRDS.Bind( wx.EVT_BUTTON, self.aws_rds_reload ) self.treeCloudfront.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.aws_cloudfront_load_details ) self.buttonReloadCloudfront.Bind( wx.EVT_BUTTON, self.aws_cloudfront_reload ) @@ -766,6 +779,15 @@ def aws_s3_refresh_bucket( self, event ): def aws_s3_open_mgmt_console( self, event ): event.Skip() + def aws_s3_selected_key( self, event ): + event.Skip() + + def aws_s3_upload_file( self, event ): + event.Skip() + + def aws_s3_drop_file( self, event ): + event.Skip() + def aws_rds_reload( self, event ): event.Skip() diff --git a/src/ui_aws_s3.py b/src/ui_aws_s3.py index 1dedff0..4af6eb6 100644 --- a/src/ui_aws_s3.py +++ b/src/ui_aws_s3.py @@ -6,6 +6,9 @@ import iconsaws import webbrowser +NEWKEY_DEFAULT = 'Select an object or key to upload a new file.' +NEWFILE_DEFAULT = 'Drag and drop a file here to upload it to the selected key.' + def aws_s3_reload(self, event): # first, create the icons for the tree and assign them to the tree @@ -18,35 +21,76 @@ def aws_s3_reload(self, event): root = self.treeS3.AddRoot('Buckets') for bucket in buckets: bucket_name = bucket['Name'] - bucket_creation_date = bucket['CreationDate'] - item = self.treeS3.AppendItem(root, bucket_name + ' (' + bucket_creation_date.strftime('%Y-%m-%d %H:%M:%S') + ')') + item = self.treeS3.AppendItem(root, bucket_name) self.treeS3.SetItemImage(item, s3_buckets, wx.TreeItemIcon_Normal) self.treeS3.ExpandAll() +# Load all details def aws_s3_load_details(self, event): + def add_paths_to_tree_ctrl(treectrl, paths): + root = treectrl.GetRootItem() + if not root: # If there's no root item, create one. + root = treectrl.AddRoot("Root") + + for path in paths: + components = path.split('/') + parent = root + for component in components: + parent = find_or_create_node(treectrl, parent, component) + + def find_or_create_node(treectrl, parent, component): + child, cookie = treectrl.GetFirstChild(parent) + while child.IsOk(): + if treectrl.GetItemText(child) == component: + return child + child, cookie = treectrl.GetNextChild(parent, cookie) + + # If the component does not exist, create it. + return treectrl.AppendItem(parent, component) + # get the selected item item = self.treeS3.GetSelection() if not item: return # get the text of the selected item - text = self.treeS3.GetItemText(item) + bucket_name = self.treeS3.GetItemText(item) # get the bucket name - bucket_name = text.split(' ')[0] + self.textCtrlS3_Details_BucketName.SetValue(bucket_name) + # load the objects of the bucket objects = aws_s3.get_s3_bucket_objects(settings.read_config()['region'], bucket_name) # clear the tree control self.treeCtrlS3_Objects.DeleteAllItems() - root = self.treeCtrlS3_Objects.AddRoot('Objects') + self.treeCtrlS3_Objects.AddRoot('/') for obj in objects: object_name = obj['Key'] - object_size = obj['Size'] - object_last_modified = obj['LastModified'] - self.treeCtrlS3_Objects.AppendItem(root, object_name + ' (' + str(object_size) + ' bytes) - ' + object_last_modified.strftime('%Y-%m-%d %H:%M:%S')) + add_paths_to_tree_ctrl(self.treeCtrlS3_Objects, [object_name]) self.treeCtrlS3_Objects.ExpandAll() +def aws_s3_selected_key(self, event): + # get the selected item + item = self.treeCtrlS3_Objects.GetSelection() + if not item: + return + # read the full path of the selected item + path = [] + while item: + path.insert(0, self.treeCtrlS3_Objects.GetItemText(item)) + item = self.treeCtrlS3_Objects.GetItemParent(item) + text = '/'.join(path) + # remove all leading slashes + text = text.lstrip('/') + + # if the item has a child node, add a / at the end, because this is a folder + if self.treeCtrlS3_Objects.ItemHasChildren(self.treeCtrlS3_Objects.GetSelection()): + text += '/' + + self.textCtrlS3_SelectedKey.SetLabel(text) + + def aws_s3_refresh_bucket(self, event): aws_s3_load_details(self, event) @@ -61,3 +105,27 @@ def aws_s3_open_mgmt_console(self, event): bucket_name = text.split(' ')[0] # open the management console webbrowser.open('https://s3.console.aws.amazon.com/s3/buckets/' + bucket_name + '?region=' + settings.read_config()['region']) + + +def aws_s3_upload_file(self, event): + # check first, if default text is still in the text control. If so, show a message box and return + if self.staticTextS3_Upload_DragZone.GetLabel() == NEWFILE_DEFAULT or self.textCtrlS3_SelectedKey.GetValue() == NEWKEY_DEFAULT: + wx.MessageBox("Please drop a file and select/enter a key before uploading.", "Warning", wx.OK | wx.ICON_WARNING) + return + + # if both are set, upload the file + aws_s3.upload_file(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), self.staticTextS3_Upload_DragZone.GetLabel(), self.textCtrlS3_SelectedKey.GetValue()) + # show a message box that the file was uploaded + wx.MessageBox("File uploaded successfully.", "Success", wx.OK | wx.ICON_INFORMATION) + # clear the text control + self.staticTextS3_Upload_DragZone.SetLabel(NEWFILE_DEFAULT) + self.textCtrlS3_SelectedKey.SetValue(NEWKEY_DEFAULT) + # refresh the bucket + aws_s3_load_details(self, event) + + +def aws_s3_drop_file(self, event): + # read the dropped file + file_path = event.GetFiles()[0] + # show the file in the static text + self.staticTextS3_Upload_DragZone.SetLabel(file_path) From 71004ab425064bc6fd3cf8121c70556dd891b56d Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Thu, 4 Jul 2024 22:35:55 +0200 Subject: [PATCH 3/5] #14 Download object, delete object --- icons/Delete.png | Bin 0 -> 2092 bytes icons/Download_from_the_Cloud.png | Bin 0 -> 3010 bytes src/AWSManager.fbp | 35 +++++++++- src/aws_s3.py | 18 +++++ src/awsmanager.py | 10 +++ src/gui.py | 22 ++++++- src/icons.py | 105 ++++++++++++++++++++++++++++++ src/ui_aws_s3.py | 61 +++++++++++++++-- 8 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 icons/Delete.png create mode 100644 icons/Download_from_the_Cloud.png diff --git a/icons/Delete.png b/icons/Delete.png new file mode 100644 index 0000000000000000000000000000000000000000..79440458032b88f042bffa4d9566f16006c9897c GIT binary patch literal 2092 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 aws_s3_selected_key + + S3 Operations + menuS3_Object + protected + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Download + menuItemS3_DownloadObject + none + + + aws_s3_menu_download_object + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Delete + menuItemS3_DeleteObject + none + + + aws_s3_menu_delete_object + + @@ -5495,7 +5528,7 @@ 0 0 wxID_ANY - Drag a file here to upload it to the S3 bucket + Drag and drop a file here to upload it to the selected key. 0 0 diff --git a/src/aws_s3.py b/src/aws_s3.py index 639ae8a..64e9519 100644 --- a/src/aws_s3.py +++ b/src/aws_s3.py @@ -19,3 +19,21 @@ def get_s3_bucket_objects(region, bucket_name): for obj in objects['Contents']: objects_list.append(obj) return objects_list + + +# download an object from a bucket into given file +def download_object(region, bucket_name, object_name, file_name): + s3 = boto3.client('s3', region_name=region) + s3.download_file(bucket_name, object_name, file_name) + + +# upload a file to a bucket +def upload_file(region, bucket_name, file_name, object_name): + s3 = boto3.client('s3', region_name=region) + s3.upload_file(file_name, bucket_name, object_name) + + +# delete an object from a bucket +def delete_object(region, bucket_name, object_name): + s3 = boto3.client('s3', region_name=region) + s3.delete_object(Bucket=bucket_name, Key=object_name) diff --git a/src/awsmanager.py b/src/awsmanager.py index 1848fa5..32c9abc 100644 --- a/src/awsmanager.py +++ b/src/awsmanager.py @@ -41,6 +41,10 @@ def __init__(self, parent): self.m_auinotebook1.SetPageBitmap(self.m_auinotebook1.FindPage(self.panelS3), iconsaws.arch_amazon_simple_storage_service_48.GetBitmap().ConvertToImage().Rescale(16, 16).ConvertToBitmap()) self.m_auinotebook1.SetPageBitmap(self.m_auinotebook1.FindPage(self.panelCloudfront), iconsaws.arch_amazon_cloudfront_48.GetBitmap().ConvertToImage().Rescale(16, 16).ConvertToBitmap()) + # add the icons to the menu items of S3 + self.menuItemS3_DownloadObject.SetBitmap(icons.download_from_the_cloud.GetBitmap().ConvertToImage().Rescale(16, 16).ConvertToBitmap()) + self.menuItemS3_DeleteObject.SetBitmap(icons.delete.GetBitmap().ConvertToImage().Rescale(16, 16).ConvertToBitmap()) + def awsmanagerClose(self, event): self.Close() @@ -128,6 +132,12 @@ def aws_s3_upload_file(self, event): def aws_s3_drop_file(self, event): ui_aws_s3.aws_s3_drop_file(self, event) + def aws_s3_menu_download_object(self, event): + ui_aws_s3.aws_s3_menu_download_object(self, event) + + def aws_s3_menu_delete_object(self, event): + ui_aws_s3.aws_s3_menu_delete_object(self, event) + def aws_rds_reload(self, event): ui_aws_rds.aws_rds_reload(self, event) diff --git a/src/gui.py b/src/gui.py index 435d98d..48be170 100644 --- a/src/gui.py +++ b/src/gui.py @@ -462,6 +462,15 @@ def __init__( self, parent ): fgSizerS3Objects.Add( self.staticTextS3_Objects, 0, wx.ALL, 5 ) self.treeCtrlS3_Objects = wx.TreeCtrl( self.panelS3Details, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE ) + self.menuS3_Object = wx.Menu() + self.menuItemS3_DownloadObject = wx.MenuItem( self.menuS3_Object, wx.ID_ANY, u"Download", wx.EmptyString, wx.ITEM_NORMAL ) + self.menuS3_Object.Append( self.menuItemS3_DownloadObject ) + + self.menuItemS3_DeleteObject = wx.MenuItem( self.menuS3_Object, wx.ID_ANY, u"Delete", wx.EmptyString, wx.ITEM_NORMAL ) + self.menuS3_Object.Append( self.menuItemS3_DeleteObject ) + + self.treeCtrlS3_Objects.Bind( wx.EVT_RIGHT_DOWN, self.treeCtrlS3_ObjectsOnContextMenu ) + fgSizerS3Objects.Add( self.treeCtrlS3_Objects, 1, wx.ALL|wx.EXPAND, 5 ) @@ -497,7 +506,7 @@ def __init__( self, parent ): fgSizerS3Upload.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - self.staticTextS3_Upload_DragZone = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Drag a file here to upload it to the S3 bucket", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.staticTextS3_Upload_DragZone = wx.StaticText( self.panelS3Details, wx.ID_ANY, u"Drag and drop a file here to upload it to the selected key.", wx.DefaultPosition, wx.DefaultSize, 0 ) self.staticTextS3_Upload_DragZone.Wrap( -1 ) self.staticTextS3_Upload_DragZone.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) @@ -702,6 +711,8 @@ def __init__( self, parent ): self.buttonS3_Details_Refresh.Bind( wx.EVT_BUTTON, self.aws_s3_refresh_bucket ) self.buttonS3_Details_OpenMgmtConsole.Bind( wx.EVT_BUTTON, self.aws_s3_open_mgmt_console ) self.treeCtrlS3_Objects.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.aws_s3_selected_key ) + self.Bind( wx.EVT_MENU, self.aws_s3_menu_download_object, id = self.menuItemS3_DownloadObject.GetId() ) + self.Bind( wx.EVT_MENU, self.aws_s3_menu_delete_object, id = self.menuItemS3_DeleteObject.GetId() ) self.buttonS3_Upload.Bind( wx.EVT_BUTTON, self.aws_s3_upload_file ) self.staticTextS3_Upload_DragZone.Bind( wx.EVT_DROP_FILES, self.aws_s3_drop_file ) self.buttonReloadRDS.Bind( wx.EVT_BUTTON, self.aws_rds_reload ) @@ -782,6 +793,12 @@ def aws_s3_open_mgmt_console( self, event ): def aws_s3_selected_key( self, event ): event.Skip() + def aws_s3_menu_download_object( self, event ): + event.Skip() + + def aws_s3_menu_delete_object( self, event ): + event.Skip() + def aws_s3_upload_file( self, event ): event.Skip() @@ -818,6 +835,9 @@ def m_splitter11OnIdle( self, event ): self.m_splitter11.SetSashPosition( 0 ) self.m_splitter11.Unbind( wx.EVT_IDLE ) + def treeCtrlS3_ObjectsOnContextMenu( self, event ): + self.treeCtrlS3_Objects.PopupMenu( self.menuS3_Object, event.GetPosition() ) + def m_splitter12OnIdle( self, event ): self.m_splitter12.SetSashPosition( 0 ) self.m_splitter12.Unbind( wx.EVT_IDLE ) diff --git a/src/icons.py b/src/icons.py index b0c2bab..ca0dd50 100644 --- a/src/icons.py +++ b/src/icons.py @@ -80,6 +80,111 @@ index.append('cancel') catalog['cancel'] = cancel +#---------------------------------------------------------------------- +delete = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAAARzQklU' + b'CAgICHwIZIgAAAfWSURBVHic7Z27jxxFEId/NesXEuKMhIxlATohY5HZlogg4IgcEGAJ4dAp' + b'IZAhIyKERYZJCPgTyMhIQCaADPmwkIWNkBYOix0hGWOfAXO7XQQzs/Pornl27cN0BXcz/aqe' + b'r6arq3t2Z4EgQYIECRIkSJAgQYIECRLk/yS07A4AwKG3r2zeN/QhgFMANpXVjQFsH4z4rX8+' + b'OD1W1tUoSzdACv8KgMMLVn37YMSnl22EaJnKAeA+4xIWDx8ADqe6lypLNwAYW8vTTSeXpjuV' + b'fcvuACjamHtCiorp1XLglh6TwACbcmLxfH7Mm536qiCLmQPe/fFFmNlhMCWTLPEmmDahP+G2' + b'lTGIx2Aap8fbiEa38d4zX2kr9muACzdOgfgkGJsg3loxyH0lM85lEMZg+g4XT2z7anyYAd75' + b'4RUg2gLMKTBteejP+gjxZSDaBsxlvP/sZ72b6d2BCzcuAfxG7/oPlNBHuHjizT41B0RBAX4u' + b'/VkMMcDP/es+aNKfxQADRGeDEYCEQXS2b21vUdDpL259TaDnfbUHLv0TE9mRbSVxXaYzx6Ej' + b'L8yMb75/+bEXXNW6ireFGIFiLw2xC8hqgJ8Lwc+1wqMBGBzTkAG1TPBcPZUUzmX1DNB7BEjg' + b'LSjSSSFJGXzePE3s3H7icQTQhITbRaiwEPBNbiY5rfNJrubNKo4AbtcpC0p3NzNPXjj4VIyn' + b'+Q4eDWAwiiPM5AIPAngAYMYU0eq5oBFMLF28F/BcU6AFeDGicepwZ2bZMxp5GwHeHsjsTTfK' + b'd0USLxeulTEnxaUUx0mhGQaY2V3A0mG3lRzaum0dwoUxg7k0PeORoxs7QunO4s0AV8/QPTD2' + b'fIBnVMFXM5XBpx2oggcANrz77XO056jVS7w+kjTAjg/wqANv5/gHDxt81hZ5XIQBng2QREKF' + b'21MTfMUr5djc4PNR5ei4BN7SARiPizDA8zNhNjwhylfD1sVUy5f/WJlOt2yBZTGvrvlipvNu' + b'l04NvEVAgGcDECXxcelud4hX8I065MzW4Evp/tYAgO8RAI4BWmHw7K7aCnySQNEqG4Bp4tqO' + b'Wwh4CXqa2U6HOz1LYAZ4lV1QMjwLcVCdL2oBvnS/Lhy86zoAM1rhEUBsYhClHZZ9hCp4yb87' + b'2uoCPpNoleeA6b5RPJo69oPqWK4J+KwI82h1XVA03ZgAt/KEFQIvh8TtwGf/rh/b+FUo1Uu8' + b'LsSunqF7QLIdkbka61oLiYtaPHE1r3JSr6NQkrELj9sQgMKno41JtiOcUOanBWw14J23dRN4' + b'1+lQ8HmGV/cDaHw8vbhXIoFnO29eXATP3cBbOhiZq+oMPtPNfrchAI2Pp7MdJy978dSsQ6rK' + b'1WKrbwBijpHuB63K4knWIVW1wGcH3l2QdwMYopiWBr5FRFMo1gF8Ip63ogEVFyQ/nF92DO9o' + b'zllJGlXrMQcQJo6Ou6UleIE1FgV+nkz+oyD/BphRjKjB/0rgpbu95qQzeEeFRvB5+hqMgGgU' + b'M0/deesLHgBjNFsDA5iH/o2jvyrLi67gawoOBd8E3a6Sn1x/+nGv2xCAwkLs2ktHdgEky/W+' + b'i6dKDudN2ZLWsauWK8g6XFW4XDA5vOt7GwLQ+qI2m50snPQB3gnMF3h2gbd1GIUICFAygGHE' + b'g8DzAsHXdLCYSgr7QIDaN+XThxatfHwL/14oNtTHS/5dDHuzBKURoPWqgonzgnxFNI5KauCz' + b'Q4VVMKBnANsFOU7WAXx2qjUHqBiAiWKyPrw5ALyHGL5Zh9Sd5DrWag7gGeL8A3LrDT4Tomh9' + b'RgBGNIHJXw+zzMVTZ/BSZxVWwYCSAabRNN5v5E/I6YB3h1yDwadZM14jF3Rgl2I+6MjwBb7k' + b'HfqBz3VIvqxcZXzn6E2hN4NEZSF27dyRXTDyZbtjYZOkM7osnrLiDEchUYerGFu6iwUcX/64' + b'i9f9b0MAiu+MY+Yd3+ArpfPDzuBdHRayGGCj434A3Zf2xergXVU9gi8kqkzAgOJL+5gQ55Fo' + b'Nx/fefEkTaxVRy7qcHcuO9RaBQOab000mID8gm8d0XgCn6fruSA1A2RfZmu6UwGP4BtCSXdG' + b'fiD3VWcRBmi6oMxvykwWFsNLHcwOGt0ZtXwNQw9RM4BhxNIMvwrgrQekog69fSBA0wARJlHx' + b'5bUlt9wPfG43yZfVDLge4PNzf68mqIreHJBu34r+vXDaDnwNXSXwhRw1A6itA/7co3iZi6dc' + b'R+UjAU4dEB6BJgq0tiEARQP8fu7ILpj3LPBYAHi0B293JleQLgTvaG1DAMqvr2fCjgieqwnN' + b'4LkRPJfBizrs9HluBj5LUoyAAO3fDzD5doTLG5WTmsHbGaiAb9LhznOCn1+Dv/fDuUT19wPK' + b'2xGOfEAgm+bXTLrZQd0cn+sQNTSHq8ojQNUAJDzIHgp+YETTSkd+6Pd7wVXRHQHgSfFdorXg' + b'hfk2r+gBfAsdVnXFrWhA+ydMOIo525BbN/C57rUeAdepK/gSECXwEnRHgwb7bria8CXqvyFz' + b'/NPf7gJ4eJ7gATwL6UUFQ8EDADPf/uX8k4+6mvIl6j9jxeDXgDYxvBVMDo/hnTocIXFlGcwA' + b'DPPfM9CrrS5ygKgb4Kdzxz7n2YGnwPwJGHfmGVYMXyG0CPCwwTPzH2zwMfYfOn7z/BNfdr/i' + b'IEGCBAkSJEiQIEGCBAkSJIgs/wGKIXp9X+ZuwAAAAABJRU5ErkJggg==') +index.append('delete') +catalog['delete'] = delete + +#---------------------------------------------------------------------- +download_from_the_cloud = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAAARzQklU' + b'CAgICHwIZIgAAAtsSURBVHic7Zx7jFx1Fcc/5+7M7M5uy0Lb1QIt7S70oTyiwfgKxoiBliql' + b'NJIIARQ1QGggviCExFDR0hcoCAb7cttSKBbbkBj+EDWYGFSkQIVSoWpr290ttLLbfXRnZ3bu' + b'7/jH3XnfO4+dO7NLvN+/9t575ne+53vO7/x+987chQABAgQIECBAgAABAgQIECBAgAABAgT4' + b'f4BMNAEvzHtqpAN79GMNyiUGvVTQi0HmAKgCKAonVDklcEpVjwKvqco+EWvvv74x9eSEBlAm' + b'JlUCFm4fXA56jSJLBGaAjontICV87nHq76wDwBj2iegetaxnDt3S+s/aMh8/JjwBC3fG5pJM' + b'fk+N3iQirc5ZN+Gd87nHhcJnJyl9SXlVLdmgVuuT/7lFRvyOoRpMWAI+2jk0Uxv0foXbM2er' + b'Fb5wjBxbpV/RrapNa4/c1nK82hj8wIQkYMH2wdsFXQ8yxTlTY+HzxgAzaKvcf+z4tEdZKWac' + b'YfiCuibggh3DsxqMvU3gcueMP8JXYptjB29im1uO3tH2aqWx+IW6JWDh9sHlinYKcoZ7tYKb' + b'mHWYHQnbyI3dd0x/toJwfINVDycLtw88BOwW5AzVQkEdMXTs7/QVsm0z1wptHTvNG7NwjHzb' + b'MbuIJbpr9hP/XVWD0Eui5jNg4fbBncBXy9qtpK+59e5arQdZtsrOrhVtN3hH4z9qloBZuzTa' + b'Ehv6DegXs8/7I3zmfL5tda1NQfXJ7hUf+hoieSPVBqFaDdwcG3wBuCx1XGvhK7EtmiTkprMf' + b'P5k4Dt/yis1P1GQNWLB14NcyJn52j08dp+DWu916fMo2ewzv9cDbttz1R0S/OfOx937ghxal' + b'4HsLmr9t4D6BVVXfvbrY1rst2bD4xJ0f/i01hK8JWLBt6BJV8yoQqs2iWc/1ABTtT5jIJX3f' + b'nnaUGsHXBMzbOrBf0AuhxrsVait8vq1CD0a7QI8r0qUif0+Y6O6B77b2UiV8S8C8zoG7RXTd' + b'xAlfia0/SVL09wZ+pfHos333TutnHPAlAXM7h2aGSB4SJJomV0ZlOX/XN0nlrz+VxGBiimwa' + b'sSJrhr/TVtFDPl92QSGSqxzx63b3mrri4s9tB+Tlr5BXxraSGCQqcFeTHf/3Weu772allq1r' + b'1TOgo7N/XgMcrMfdq6qioyNgkqjJ82cJIhaEm8gOayJ2XKq618ANA/fMLvlFUNUzQIzeXVid' + b'btXiJmiKdOmKV1U0PoSJDWIPncKc7sMe6sMe6sWc7sUM9WHHhjDx4YIxcv17VXwlMXjbAojI' + b'Jyzk4Bnrum4uqV8pg2K44GfayJRT76tKyxjFNLFMcJnzudcqWwhNPIYdG2RxRyP3XX4u82Y0' + b'5Xz24MkYP/xdF384kkCapiLhphL+az87VDEqeuPAPeftxAPVzYApA9c54ntXVqlqyVzLtR27' + b'kp4damxIJli35LwC8QHmt0VZddV5mEQcNbbP68H4ZgdgifL01NXHrvWSsKoEGGMW1WpKF7Ql' + b'o6ixaZsS9uQz96xGVA1qjMuYXsKn/NUuBhHdM/XBo0vdOFeVAIXP1Vz4vOosi1XNdlzjjwHR' + b'p5vXHj0nn+24noZ2bOhtVcv6iqBzUqRznI0Rzj0u1o9LrwfliZ+xlTxepf258Sq0LbamlLBt' + b'kaRuBa7M/kRFM6Bjc+8N7Zv6XqBBTono5uqrJVVxuaSLfUNWHrwrvlAktyeymTHybd2Lorz7' + b'ERGuiP74yHXZTMtKQMem/iXtm/peB3lKhCv8JJ09RvHH1uUwxUUIqG1bquxGUER/wsrD6V1E' + b'0RY0a3P/tAj2DjBXTcSULvBXjGwer/wW5jlmnWNAmRUNcXsMHoEiM2Duxv5FEczbIFdNiscG' + b'ZGyLwbviCwWtewwpW+We1HnXBLRv6VsqlnlelbbJQjp3zGLwY8dVPIYU1ypa69mRHx1eBi4J' + b'mLu5fzE2u1Ft8JP0uKsFcvyVQrapnzeCfsdrqV4PeWtA++a+OWqSexAJ5RPJFyITDOTb5guW' + b'bZt7rbJeWu4uKH821j8GL3/ZvnQJK9XKXYSN7hCR6GQlXQ5UdeweYPLG4BzKlLB16NJ0AuZu' + b'ev9WVS7Dw+FkIF1WLtKzfnLGkDuGftpJwC6NaF/fqtRvkSYt6fI3ojWPoRJbz3hVPhICmNPX' + b'd6uIzpiswheO643UTJH0sVsMXtzcbd25VR+vKvPHFlu9N9dBtaQrT5IaA8k4xrbJ6iPjXg9M' + b'bKCQlwhYDWhDIyJSIoZKhPfSrHgMgp4Tmv1E70Uieq73IM5ANa+WRAyTiKGjw6C5zaYS4VUV' + b'+3Sfqz8QiDQjIRuNtPgQQyXCZ86njhXODYllLnefNs4H/Be+kLQaG2OS6OgwP7+2nes/3sZ4' + b'MbD6M57XOl9+lzv3HAYrBMZGLGtChE/ZqmpTSJUvCBMjfIa0Ol+4qHLl/DOpFT5/QWsuNy3k' + b'leFWOoZKbF2TpAxaanRuXlbSxs6CltuPNYt0uY8N3B9TZGyRBqyGMISiLN/2Nv0jSfxGfyzJ' + b'zTveQRsaUQkhlpWO1y2G1LFbDNXGm9FQ+ixE2vKFTw3iJny1pN2IAGi4CSvSzBsnbJZu+QdD' + b'cbuooJWgP5ZkyYb9vP5eEmlsgXCjS6GluNWu0AoKW8xBCzXT89tN+c86/CMtImg4ikRaeONE' + b'kmWd/iRhKG6zZMN+9p1IIpFmNJT53VBxkYo9Aa5S+LRmst9SJVlcpGLC+0taBIhEIdLC3p5R' + b'rvlldUkYitt8eeN+9p0YhXAzGm52Ej3uGCp5AlxM+LFrhpctVemprfCVkU5tFZ0kJFi65cC4' + b'kpAS/289CQi3OGMi44ohw83b1k2zlK1blwCFxugfLRHtnnjhC2/ciESRRmcmLN18gOFE+UkY' + b'itt8aYMjvkQy4rvFkPLvFkNt49W9rJzdaxnVHi8hvIiUQ9qNSGbM0qRBnJbR2MLe4wmWbSkv' + b'CSnxXznuiK/h5ix/k6jQVDYCWIj1JzfhJ0NbEgENO2vCX7oTLCsxE4YTGfEJtziLuuQLQV1j' + b'8JjhpxhtfRrAitvW87kO3ISfONLO7qgZws38uTvumYThhM3STW+N9fxmZzFP9/xi/txioIIY' + b'KBlDAZRf8NDM0wBW713Tu4zypm97W5+Ezx7T2R01Q7iFl7riXLP5ACOjmf+xMZywuXrjW7x0' + b'bMTZaoZTPX+8MXjbFgqfsS0pvIMRkpFHUwchABF2q+HibCuvvp17nE2w0DafRLZt7jU32/wx' + b'xx6kobx0bJhPPvwa3798Ngnb8NMXuzg0QLrnZ550at6YXv5rE68HHmB9+7upAwE4a0NvayQW' + b'P4TItIkmXSpJagyMxjCjcbBHHduGMDQ0Qjg69oBtUgoPygFWL7gw+5QF0HfbtH5VVtfslttl' + b'So934Ucsp8ojUaSxGSJTxnp+M4gUaYO1j6EolH4arIJfSOe8oNH2SM8xVGZNpoovz59bxXv5' + b'c+Pl5a/Kis+GsRaxZt4L+adzfxekoatRE5tMFV/cn//bRMUrhpRtvoTlQG9yEx9cXlGa8XD3' + b'UhV5TkCqrxav6ixd8ZXY1nt2lA3VUURu5sEFz3iZuL4jNv3h7hXA44VkajGlx5+kSSu889F+' + b'RK/lwYUvFjPzfElv2kM9i0B3AR7/Yqyewley/kyw8M7n30Eii1jVcaSUZdG3JFvXvXe+JYnn' + b'QC5KDz1phffyV0fhVXtQWc2aBY+XNnZQ+jXVlWqd2dzzdTAPAGO/nsjx+gES3stftcJzCEvW' + b'MyCdPDYvXslHy39PeOXhpqnRhhWislyEz/olfCW29UhShdiHkXWsme/5HnApjO9F7bUnp04x' + b'w4sF+RTQoartItoO0vpBnR0loXoa5DVE/4rhFTT8MmvPr9n/EQoQIECAAAECBAgQIECAAAEC' + b'BAgQIECAAAECBAgQIECAAB98/A8DIpRkVa3vWwAAAABJRU5ErkJggg==') +index.append('download_from_the_cloud') +catalog['download_from_the_cloud'] = download_from_the_cloud + #---------------------------------------------------------------------- get_help = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAAARzQklU' diff --git a/src/ui_aws_s3.py b/src/ui_aws_s3.py index 4af6eb6..f579780 100644 --- a/src/ui_aws_s3.py +++ b/src/ui_aws_s3.py @@ -5,6 +5,7 @@ import settings import iconsaws import webbrowser +import os NEWKEY_DEFAULT = 'Select an object or key to upload a new file.' NEWFILE_DEFAULT = 'Drag and drop a file here to upload it to the selected key.' @@ -70,12 +71,7 @@ def find_or_create_node(treectrl, parent, component): self.treeCtrlS3_Objects.ExpandAll() -def aws_s3_selected_key(self, event): - # get the selected item - item = self.treeCtrlS3_Objects.GetSelection() - if not item: - return - # read the full path of the selected item +def aws_s3_get_full_path(self, item): path = [] while item: path.insert(0, self.treeCtrlS3_Objects.GetItemText(item)) @@ -88,6 +84,17 @@ def aws_s3_selected_key(self, event): if self.treeCtrlS3_Objects.ItemHasChildren(self.treeCtrlS3_Objects.GetSelection()): text += '/' + return text + + +def aws_s3_selected_key(self, event): + # get the selected item + item = self.treeCtrlS3_Objects.GetSelection() + if not item: + return + # read the full path of the selected item + text = aws_s3_get_full_path(self, item) + self.textCtrlS3_SelectedKey.SetLabel(text) @@ -113,8 +120,12 @@ def aws_s3_upload_file(self, event): wx.MessageBox("Please drop a file and select/enter a key before uploading.", "Warning", wx.OK | wx.ICON_WARNING) return + file_name_with_extension = os.path.basename(self.staticTextS3_Upload_DragZone.GetLabel()) + target_key = self.textCtrlS3_SelectedKey.GetValue() + source_file = self.staticTextS3_Upload_DragZone.GetLabel() + # if both are set, upload the file - aws_s3.upload_file(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), self.staticTextS3_Upload_DragZone.GetLabel(), self.textCtrlS3_SelectedKey.GetValue()) + aws_s3.upload_file(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), source_file, target_key + file_name_with_extension) # show a message box that the file was uploaded wx.MessageBox("File uploaded successfully.", "Success", wx.OK | wx.ICON_INFORMATION) # clear the text control @@ -129,3 +140,39 @@ def aws_s3_drop_file(self, event): file_path = event.GetFiles()[0] # show the file in the static text self.staticTextS3_Upload_DragZone.SetLabel(file_path) + + +def aws_s3_menu_download_object(self, event): + # get the selected item + item = self.treeCtrlS3_Objects.GetSelection() + if not item: + return + # read the full path of the selected item + s3object = aws_s3_get_full_path(self, item) + # ask the user where to save the file + dlg = wx.FileDialog(self, "Save File", wildcard="All files (*.*)|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + # download the object + aws_s3.download_object(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), s3object, dlg.GetPath()) + # show a message box that the object was downloaded + wx.MessageBox("Object downloaded successfully.", "Success", wx.OK | wx.ICON_INFORMATION) + dlg.Destroy() + + +def aws_s3_menu_delete_object(self, event): + # get the selected item + item = self.treeCtrlS3_Objects.GetSelection() + if not item: + return + # read the full path of the selected item + s3object = aws_s3_get_full_path(self, item) + # ask the user if he really wants to delete the object + dlg = wx.MessageDialog(self, f"Are you sure you want to delete the object '{s3object}'?", "Delete Object", wx.YES_NO | wx.ICON_QUESTION) + if dlg.ShowModal() == wx.ID_YES: + # delete the object + aws_s3.delete_object(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), s3object) + # show a message box that the object was deleted + wx.MessageBox("Object deleted successfully.", "Success", wx.OK | wx.ICON_INFORMATION) + # refresh the bucket + aws_s3_load_details(self, event) + dlg.Destroy() From ced5c6fefce017052c84105432ae6bfdb27d0c2e Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Thu, 4 Jul 2024 22:42:17 +0200 Subject: [PATCH 4/5] #14 Preload the filename for download --- src/ui_aws_s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui_aws_s3.py b/src/ui_aws_s3.py index f579780..6957976 100644 --- a/src/ui_aws_s3.py +++ b/src/ui_aws_s3.py @@ -149,8 +149,9 @@ def aws_s3_menu_download_object(self, event): return # read the full path of the selected item s3object = aws_s3_get_full_path(self, item) + filename = self.treeS3.GetItemText(item) # ask the user where to save the file - dlg = wx.FileDialog(self, "Save File", wildcard="All files (*.*)|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + dlg = wx.FileDialog(self, "Save File", defaultFile=filename, wildcard="All files (*.*)|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: # download the object aws_s3.download_object(settings.read_config()['region'], self.textCtrlS3_Details_BucketName.GetValue(), s3object, dlg.GetPath()) From 56d37d40cae2d93b3b79902d04d36fc9f21c9dc1 Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Sat, 6 Jul 2024 22:00:43 +0200 Subject: [PATCH 5/5] #14 Also fixed the usage of credentials --- src/AWSManager.fbp | 207 +++++++++++++++++++------------------ src/aws_cloudfront.py | 13 ++- src/aws_ec2.py | 22 ++-- src/aws_lambda.py | 10 +- src/aws_rds.py | 4 +- src/aws_s3.py | 13 ++- src/aws_session_handler.py | 20 ++++ src/configuration_ui.py | 42 ++++++-- src/gui.py | 4 + src/helper.py | 2 +- src/requirements.txt | 4 +- src/settings.py | 73 +++++++++---- src/ui_aws_ec2.py | 4 +- 13 files changed, 261 insertions(+), 157 deletions(-) create mode 100644 src/aws_session_handler.py diff --git a/src/AWSManager.fbp b/src/AWSManager.fbp index 19a9997..799ce2c 100644 --- a/src/AWSManager.fbp +++ b/src/AWSManager.fbp @@ -182,7 +182,7 @@ 5 wxEXPAND | wxALL 1 - + 1 1 1 @@ -237,7 +237,7 @@ - + EC2 0 @@ -4333,11 +4333,11 @@ - + S3 1 - + 1 1 1 @@ -4389,16 +4389,16 @@ wxTAB_TRAVERSAL - + bSizer81 wxVERTICAL none - + 5 wxEXPAND 1 - + 1 1 1 @@ -4652,8 +4652,8 @@ - - + + 1 1 1 @@ -4705,7 +4705,7 @@ wxTAB_TRAVERSAL - + 1 wxBOTH 0 @@ -4717,11 +4717,11 @@ none 3 0 - + 5 wxEXPAND 1 - + 4 wxBOTH 1 @@ -4733,11 +4733,11 @@ none 0 0 - + 5 wxALL 0 - + 1 1 1 @@ -4795,11 +4795,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -4860,11 +4860,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -4935,11 +4935,11 @@ aws_s3_refresh_bucket - + 5 wxALL 0 - + 1 1 1 @@ -5012,11 +5012,11 @@ - + 5 wxEXPAND 1 - + 2 wxBOTH 1 @@ -5028,11 +5028,11 @@ none 0 0 - + 5 wxALL 0 - + 1 1 1 @@ -5090,11 +5090,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -5148,11 +5148,11 @@ aws_s3_selected_key - + S3 Operations menuS3_Object protected - + 0 1 @@ -5166,7 +5166,7 @@ aws_s3_menu_download_object - + 0 1 @@ -5185,11 +5185,11 @@ - + 5 wxEXPAND 1 - + 3 wxBOTH 1 @@ -5201,21 +5201,21 @@ none 0 0 - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -5273,21 +5273,21 @@ 400 - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL 0 - + 1 1 1 @@ -5345,11 +5345,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -5410,11 +5410,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -5485,21 +5485,21 @@ aws_s3_upload_file - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -5558,11 +5558,11 @@ aws_s3_drop_file - + 5 wxEXPAND 1 - + 0 protected 0 @@ -5956,11 +5956,11 @@ - + Cloudfront 0 - + 1 1 1 @@ -6012,16 +6012,16 @@ wxTAB_TRAVERSAL - + bSizer83 wxVERTICAL none - + 5 wxEXPAND 1 - + 1 1 1 @@ -6275,8 +6275,8 @@ - - + + 1 1 1 @@ -6328,16 +6328,16 @@ wxTAB_TRAVERSAL - + bSizer132 wxVERTICAL none - + 5 wxEXPAND 1 - + 3 wxBOTH 1 @@ -6349,11 +6349,11 @@ none 0 0 - + 5 wxALL 0 - + 1 1 1 @@ -6411,11 +6411,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -6476,11 +6476,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -6551,11 +6551,11 @@ aws_cloudfront_refresh_distribution - + 5 wxALL 0 - + 1 1 1 @@ -6613,11 +6613,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -6678,11 +6678,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -6753,11 +6753,11 @@ aws_cloudfront_open_mgmt_console - + 5 wxALL 0 - + 1 1 1 @@ -6815,11 +6815,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -6880,21 +6880,21 @@ - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL 0 - + 1 1 1 @@ -6952,11 +6952,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -7017,11 +7017,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -7092,11 +7092,11 @@ aws_cloudfront_invalidate - + 5 wxALL 0 - + 1 1 1 @@ -7154,11 +7154,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -7219,21 +7219,21 @@ - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL 0 - + 1 1 1 @@ -7291,11 +7291,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -7356,21 +7356,21 @@ - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL 0 - + 1 1 1 @@ -7428,11 +7428,11 @@ -1 - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -7490,11 +7490,11 @@ - + 5 wxEXPAND 1 - + 0 protected 0 @@ -7514,7 +7514,7 @@ - + 0 wxAUI_MGR_DEFAULT @@ -7542,7 +7542,8 @@ - + showConfig + 1 wxBOTH diff --git a/src/aws_cloudfront.py b/src/aws_cloudfront.py index 7341586..021fdbc 100644 --- a/src/aws_cloudfront.py +++ b/src/aws_cloudfront.py @@ -1,10 +1,12 @@ import boto3 +import aws_session_handler import json # get all cloudfront distributions and return a list of distributions def get_cloudfront_distributions(): - cloudfront = boto3.client('cloudfront') + session = aws_session_handler.get_session() + cloudfront = session.client('cloudfront') distributions = cloudfront.list_distributions() distributions_list = [] for distribution in distributions['DistributionList']['Items']: @@ -14,20 +16,23 @@ def get_cloudfront_distributions(): # get information about a cloudfront distribution def get_cloudfront_distribution(distribution_id): - cloudfront = boto3.client('cloudfront') + session = aws_session_handler.get_session() + cloudfront = session.client('cloudfront') distribution = cloudfront.get_distribution(Id=distribution_id) return distribution # invalidate a cloudfront distribution def invalidate_cloudfront_distribution(distribution_id, paths): - cloudfront = boto3.client('cloudfront') + session = aws_session_handler.get_session() + cloudfront = session.client('cloudfront') response = cloudfront.create_invalidation(DistributionId=distribution_id, InvalidationBatch={'Paths': {'Quantity': len(paths), 'Items': paths}, 'CallerReference': 'awsmanager'}) return json.dumps(response, indent=2, default=str) # load tags of a cloudfront distribution def get_cloudfront_distribution_tags(distribution_id): - cloudfront = boto3.client('cloudfront') + session = aws_session_handler.get_session() + cloudfront = session.client('cloudfront') tags = cloudfront.list_tags_for_resource(Resource=distribution_id) return tags diff --git a/src/aws_ec2.py b/src/aws_ec2.py index 62c3a06..9d49c48 100644 --- a/src/aws_ec2.py +++ b/src/aws_ec2.py @@ -1,23 +1,27 @@ import boto3 +import aws_session_handler # get information about an ec2 instance def get_ec2_instance(region, instance_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) instance = ec2.describe_instances(InstanceIds=[instance_id]) return instance['Reservations'][0]['Instances'][0] # get all information about a volumeid def get_ec2_volume(region, volume_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) volume = ec2.describe_volumes(VolumeIds=[volume_id]) return volume['Volumes'][0] # get all ec2 instances of a region and return a list of instances def get_ec2_instances(region): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) instances = ec2.describe_instances() instances_list = [] for reservation in instances['Reservations']: @@ -28,23 +32,27 @@ def get_ec2_instances(region): # start a given instance def start_ec2_instance(region, instance_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) ec2.start_instances(InstanceIds=[instance_id]) # stop a given instance def stop_ec2_instance(region, instance_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) ec2.stop_instances(InstanceIds=[instance_id]) # reboot a given instance def reboot_ec2_instance(region, instance_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) ec2.reboot_instances(InstanceIds=[instance_id]) # terminate a given instance def terminate_ec2_instance(region, instance_id): - ec2 = boto3.client('ec2', region_name=region) + session = aws_session_handler.get_session() + ec2 = session.client('ec2', region_name=region) ec2.terminate_instances(InstanceIds=[instance_id]) diff --git a/src/aws_lambda.py b/src/aws_lambda.py index b8e6591..ad7a175 100644 --- a/src/aws_lambda.py +++ b/src/aws_lambda.py @@ -1,10 +1,12 @@ import boto3 +import aws_session_handler import json # get all lambda functions of a region and return a list of functions def get_lambda_functions(region): - lambda_client = boto3.client('lambda', region_name=region) + session = aws_session_handler.get_session() + lambda_client = session.client('lambda', region_name=region) functions = lambda_client.list_functions() functions_list = [] for lambdafunction in functions['Functions']: @@ -14,13 +16,15 @@ def get_lambda_functions(region): # get information about a lambda function def get_lambda_function(region, function_name): - lambda_client = boto3.client('lambda', region_name=region) + session = aws_session_handler.get_session() + lambda_client = session.client('lambda', region_name=region) lambdafunction = lambda_client.get_function(FunctionName=function_name) return lambdafunction # invoke a lambda function def invoke_lambda_function(region, function_name, payload): - lambda_client = boto3.client('lambda', region_name=region) + session = aws_session_handler.get_session() + lambda_client = session.client('lambda', region_name=region) response = lambda_client.invoke(FunctionName=function_name, Payload=payload) return json.dumps(response, indent=2, default=str) diff --git a/src/aws_rds.py b/src/aws_rds.py index 2f7279e..473463c 100644 --- a/src/aws_rds.py +++ b/src/aws_rds.py @@ -1,9 +1,11 @@ import boto3 +import aws_session_handler # get all rds databases of a region and return a list of rds databases def get_rds_databases(region): - rds = boto3.client('rds', region_name=region) + session = aws_session_handler.get_session() + rds = session.client('rds', region_name=region) databases = rds.describe_db_instances() databases_list = [] for database in databases['DBInstances']: diff --git a/src/aws_s3.py b/src/aws_s3.py index 64e9519..9f2016c 100644 --- a/src/aws_s3.py +++ b/src/aws_s3.py @@ -1,9 +1,11 @@ import boto3 +import aws_session_handler # get all s3 buckets of a region and return a list of buckets def get_s3_buckets(region): - s3 = boto3.client('s3', region_name=region) + session = aws_session_handler.get_session() + s3 = session.client('s3', region_name=region) buckets = s3.list_buckets() buckets_list = [] for bucket in buckets['Buckets']: @@ -23,17 +25,20 @@ def get_s3_bucket_objects(region, bucket_name): # download an object from a bucket into given file def download_object(region, bucket_name, object_name, file_name): - s3 = boto3.client('s3', region_name=region) + session = aws_session_handler.get_session() + s3 = session.client('s3', region_name=region) s3.download_file(bucket_name, object_name, file_name) # upload a file to a bucket def upload_file(region, bucket_name, file_name, object_name): - s3 = boto3.client('s3', region_name=region) + session = aws_session_handler.get_session() + s3 = session.client('s3', region_name=region) s3.upload_file(file_name, bucket_name, object_name) # delete an object from a bucket def delete_object(region, bucket_name, object_name): - s3 = boto3.client('s3', region_name=region) + session = aws_session_handler.get_session() + s3 = session.client('s3', region_name=region) s3.delete_object(Bucket=bucket_name, Key=object_name) diff --git a/src/aws_session_handler.py b/src/aws_session_handler.py new file mode 100644 index 0000000..6ace08c --- /dev/null +++ b/src/aws_session_handler.py @@ -0,0 +1,20 @@ +import boto3 +import settings + + +# use the selected profile, if no profile is given, use the default profile or the provided credentials +def get_session(): + awslogin = settings.read_config() + aws_access_key_id = awslogin.get("aws_access_key_id", None) + aws_secret_access_key = awslogin.get("aws_secret_access_key", None) + aws_session_token = awslogin.get("aws_session_token", None) + aws_profile = awslogin.get("aws_profile", None) + if aws_access_key_id and aws_secret_access_key: + session = boto3.Session( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + ) + elif aws_profile: + session = boto3.Session(profile_name=aws_profile) + return session diff --git a/src/configuration_ui.py b/src/configuration_ui.py index 6b7437b..dec4fbd 100644 --- a/src/configuration_ui.py +++ b/src/configuration_ui.py @@ -1,5 +1,6 @@ # importing wx files import wx + # import the newly created GUI file import gui @@ -18,27 +19,48 @@ def __init__(self, parent): gui.dialogAbout.SetIcon(self, icons.settings.GetIcon()) def showConfig(self, event): + # read all available profiles + profiles = settings.get_profiles() + # set the values + self.comboBoxAwsProfile.Clear() + self.comboBoxAwsProfile.Append(profiles) + # read all available regions + regions = settings.get_regions() + print(regions) + # set the values + self.comboBoxAwsDefaultRegion.Clear() + self.comboBoxAwsDefaultRegion.Append(regions) + # get the config config = settings.read_config() # set the values - self.textAwsAccessKeyId.SetValue(config['aws_access_key_id']) - self.textAwsSecretAccessKey.SetValue(config['aws_secret_access_key']) - self.textAwsSessionToken.SetValue(config['aws_session_token']) - self.comboBoxAwsProfile.SetValue(config['aws_profile']) - self.comboBoxAwsRegion.SetValue(config['region']) + self.textAwsAccessKeyId.SetValue(config["aws_access_key_id"]) + self.textAwsSecretAccessKey.SetValue(config["aws_secret_access_key"]) + self.textAwsSessionToken.SetValue(config["aws_session_token"]) + self.comboBoxAwsProfile.SetValue(config["aws_profile"]) + self.comboBoxAwsDefaultRegion.SetValue(config["region"]) self.Layout() self.Fit() def saveConfig(self, event): # save the config - settings.save_config('aws_access_key_id', self.textAwsAccessKeyId.GetValue()) - settings.save_config('aws_secret_access_key', self.textAwsSecretAccessKey.GetValue()) - settings.save_config('aws_session_token', self.textAwsSessionToken.GetValue()) - settings.save_config('aws_profile', self.comboBoxAwsProfile.GetValue()) - settings.save_config('region', self.comboBoxAwsRegion.GetValue()) + settings.save_config("aws_access_key_id", self.textAwsAccessKeyId.GetValue()) + settings.save_config( + "aws_secret_access_key", self.textAwsSecretAccessKey.GetValue() + ) + settings.save_config("aws_session_token", self.textAwsSessionToken.GetValue()) + settings.save_config("aws_profile", self.comboBoxAwsProfile.GetValue()) + settings.save_config("region", self.comboBoxAwsDefaultRegion.GetValue()) # close the dialog self.Close() def closeConfig(self, event): self.Close() + + def reloadAwsProfiles(self, event): + # read all available profiles + profiles = settings.get_profiles() + # set the values + self.comboBoxAwsProfile.Clear() + self.comboBoxAwsProfile.Append(profiles) diff --git a/src/gui.py b/src/gui.py index 48be170..47519a3 100644 --- a/src/gui.py +++ b/src/gui.py @@ -986,6 +986,7 @@ def __init__( self, parent ): self.Centre( wx.BOTH ) # Connect Events + self.Bind( wx.EVT_SHOW, self.showConfig ) self.buttonReloadAwsProfile.Bind( wx.EVT_BUTTON, self.reloadAwsProfiles ) self.buttonSave.Bind( wx.EVT_BUTTON, self.saveConfig ) self.buttonCancel.Bind( wx.EVT_BUTTON, self.cancelConfig ) @@ -995,6 +996,9 @@ def __del__( self ): # Virtual event handlers, override them in your derived class + def showConfig( self, event ): + event.Skip() + def reloadAwsProfiles( self, event ): event.Skip() diff --git a/src/helper.py b/src/helper.py index dbcbffb..a5f8242 100644 --- a/src/helper.py +++ b/src/helper.py @@ -2,7 +2,7 @@ import json import logging -VERSION = "v2024-06-09" +VERSION = "v2024-07-07" UPDATEURL = 'https://api.github.com/repos/dseichter/AWSManager/releases/latest' RELEASES = 'https://github.com/dseichter/AWSManager/releases' NAME = 'AWSManager' diff --git a/src/requirements.txt b/src/requirements.txt index b5a929c..97ff7f6 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,4 @@ wxPython==4.2.1 urllib3==2.2.2 -pyinstaller==6.8.0 -boto3==1.34.138 +pyinstaller==6.9.0 +boto3==1.34.140 diff --git a/src/settings.py b/src/settings.py index 891fcd5..679c411 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,43 +1,76 @@ import json +import boto3 +import os -CONFIGFILE = 'config.json' +CONFIGFILE = "config.json" def create_config(): # create the config file if it does not exist try: - with open(CONFIGFILE, 'r') as f: + with open(CONFIGFILE, "r") as f: data = json.load(f) except FileNotFoundError: - with open(CONFIGFILE, 'w') as f: - f.write('{}') + with open(CONFIGFILE, "w") as f: + f.write("{}") - with open(CONFIGFILE, 'r') as f: + with open(CONFIGFILE, "r") as f: data = json.load(f) - if 'aws_access_key_id' not in data: - data['aws_access_key_id'] = '' - if 'aws_secret_access_key' not in data: - data['aws_secret_access_key'] = '' - if 'aws_session_token' not in data: - data['aws_session_token'] = '' - if 'aws_profile' not in data: - data['aws_profile'] = '' - if 'region' not in data: - data['region'] = 'eu-central-1' - - with open(CONFIGFILE, 'w') as f: + if "aws_access_key_id" not in data: + data["aws_access_key_id"] = "" + if "aws_secret_access_key" not in data: + data["aws_secret_access_key"] = "" + if "aws_session_token" not in data: + data["aws_session_token"] = "" + if "aws_profile" not in data: + data["aws_profile"] = "" + if "region" not in data: + data["region"] = "eu-central-1" + + with open(CONFIGFILE, "w") as f: json.dump(data, f, indent=4, sort_keys=True) def read_config(): - with open(CONFIGFILE, 'r') as f: + with open(CONFIGFILE, "r") as f: return json.load(f) def save_config(key, value): - with open(CONFIGFILE, 'r') as f: + with open(CONFIGFILE, "r") as f: data = json.load(f) data[key] = value - with open(CONFIGFILE, 'w') as f: + with open(CONFIGFILE, "w") as f: json.dump(data, f, indent=4, sort_keys=True) + + +# get a list of all AWS profiles of the credentials file +def get_profiles(): + profiles = [] + if os.name == "posix": # Linux or macOS + credentials_file = os.path.expanduser("~/.aws/credentials") + elif os.name == "nt": # Windows + credentials_file = os.path.expanduser( + os.path.join(os.environ["USERPROFILE"], ".aws", "credentials") + ) + else: + raise OSError("Unsupported operating system") + + with open(credentials_file, "r") as f: + lines = f.readlines() + for line in lines: + if line.startswith("["): + profile = line.strip().strip("[]") + profiles.append(profile) + return profiles + + +# get a list of all AWS regions using the boto3 library +def get_regions(): + ec2 = boto3.client("ec2") + regions = ec2.describe_regions() + region_list = [] + for region in regions["Regions"]: + region_list.append(region["RegionName"]) + return region_list diff --git a/src/ui_aws_ec2.py b/src/ui_aws_ec2.py index 9103073..8d2d31f 100644 --- a/src/ui_aws_ec2.py +++ b/src/ui_aws_ec2.py @@ -69,11 +69,11 @@ def aws_ec2_load_details(self, event): self.textCtrlEC2_PublicIpAddress.SetValue(ec2_instance.get('PublicIpAddress', '')) self.textCtrlEC2_Architecture.SetValue(ec2_instance.get('Architecture', '')) # get the tags - self.propertyGridEC2_tags.Clear() + self.propertyGridEC2_Tags.Clear() # add the tags to the property grid for tag in ec2_instance['Tags']: self.propertyGridEC2_Tags.Append(wx.propgrid.StringProperty(tag['Key'], tag['Key'], tag['Value'])) - self.propertyGridEC2_tags.Refresh() + self.propertyGridEC2_Tags.Refresh() # get volume information and add it to the grid self.gridEC2_Volumes.ClearGrid() volumes = ec2_instance['BlockDeviceMappings']