sshmenuc 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sshmenuc/__init__.py +14 -0
- sshmenuc/__main__.py +6 -0
- sshmenuc/core/__init__.py +9 -0
- sshmenuc/core/base.py +108 -0
- sshmenuc/core/config.py +146 -0
- sshmenuc/core/config_editor.py +217 -0
- sshmenuc/core/launcher.py +196 -0
- sshmenuc/core/navigation.py +366 -0
- sshmenuc/json_test.json +28 -0
- sshmenuc/main.py +24 -0
- sshmenuc/ui/__init__.py +7 -0
- sshmenuc/ui/colors.py +46 -0
- sshmenuc/ui/display.py +112 -0
- sshmenuc/utils/__init__.py +6 -0
- sshmenuc/utils/helpers.py +86 -0
- sshmenuc-1.1.0.dist-info/LICENSE +9 -0
- sshmenuc-1.1.0.dist-info/METADATA +356 -0
- sshmenuc-1.1.0.dist-info/RECORD +20 -0
- sshmenuc-1.1.0.dist-info/WHEEL +4 -0
- sshmenuc-1.1.0.dist-info/entry_points.txt +3 -0
sshmenuc/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .core import ConnectionManager, ConnectionNavigator, SSHLauncher
|
|
2
|
+
from .ui import Colors, MenuDisplay
|
|
3
|
+
from .utils import setup_logging, setup_argument_parser
|
|
4
|
+
|
|
5
|
+
__version__ = "1.1.0"
|
|
6
|
+
__all__ = [
|
|
7
|
+
'ConnectionManager',
|
|
8
|
+
'ConnectionNavigator',
|
|
9
|
+
'SSHLauncher',
|
|
10
|
+
'Colors',
|
|
11
|
+
'MenuDisplay',
|
|
12
|
+
'setup_logging',
|
|
13
|
+
'setup_argument_parser'
|
|
14
|
+
]
|
sshmenuc/__main__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module per sshmenuc.
|
|
3
|
+
"""
|
|
4
|
+
from .base import BaseSSHMenuC
|
|
5
|
+
from .config import ConnectionManager
|
|
6
|
+
from .navigation import ConnectionNavigator
|
|
7
|
+
from .launcher import SSHLauncher
|
|
8
|
+
|
|
9
|
+
__all__ = ['BaseSSHMenuC', 'ConnectionManager', 'ConnectionNavigator', 'SSHLauncher']
|
sshmenuc/core/base.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common base class for all classes in the sshmenuc project.
|
|
3
|
+
Provides shared functionality and common patterns.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Any, List, Union, Optional
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseSSHMenuC(ABC):
|
|
13
|
+
"""Abstract base class with common functionality for all sshmenuc classes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config_file: Optional[str] = None):
|
|
16
|
+
self.config_file = config_file
|
|
17
|
+
self.config_data: Dict[str, Any] = {"targets": []}
|
|
18
|
+
self._setup_logging()
|
|
19
|
+
|
|
20
|
+
def _setup_logging(self):
|
|
21
|
+
"""Setup basic logging configuration."""
|
|
22
|
+
if not logging.getLogger().handlers:
|
|
23
|
+
logging.basicConfig(level=logging.INFO)
|
|
24
|
+
|
|
25
|
+
def load_config(self):
|
|
26
|
+
"""Load and normalize the configuration file.
|
|
27
|
+
|
|
28
|
+
Handles both new format (with 'targets' key) and legacy format.
|
|
29
|
+
If the file doesn't exist or is corrupted, creates an empty config.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
with open(self.config_file, "r") as f:
|
|
33
|
+
data = json.load(f)
|
|
34
|
+
if isinstance(data, dict) and "targets" not in data:
|
|
35
|
+
targets = []
|
|
36
|
+
for k, v in data.items():
|
|
37
|
+
targets.append({k: v})
|
|
38
|
+
self.config_data = {"targets": targets}
|
|
39
|
+
else:
|
|
40
|
+
self.config_data = data
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
self._create_config_directory()
|
|
43
|
+
self.config_data = {"targets": []}
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
logging.error(f"Error decoding JSON in '{self.config_file}'. Using empty configuration.")
|
|
46
|
+
self.config_data = {"targets": []}
|
|
47
|
+
|
|
48
|
+
def _create_config_directory(self):
|
|
49
|
+
"""Create configuration directory if it doesn't exist."""
|
|
50
|
+
try:
|
|
51
|
+
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logging.warning(f"Could not create config directory: {e}")
|
|
54
|
+
|
|
55
|
+
def save_config(self):
|
|
56
|
+
"""Save configuration to file.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
OSError: If file cannot be written (permission denied, disk full, etc.)
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
with open(self.config_file, "w") as file:
|
|
63
|
+
json.dump(self.config_data, file, indent=4)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logging.error(f"Error saving config: {e}")
|
|
66
|
+
|
|
67
|
+
def get_config(self) -> Dict[str, Any]:
|
|
68
|
+
"""Return the current configuration.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Configuration dictionary with 'targets' key
|
|
72
|
+
"""
|
|
73
|
+
return self.config_data
|
|
74
|
+
|
|
75
|
+
def set_config(self, config_data: Dict[str, Any]):
|
|
76
|
+
"""Set a new configuration.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
config_data: New configuration dictionary to set
|
|
80
|
+
"""
|
|
81
|
+
self.config_data = config_data
|
|
82
|
+
|
|
83
|
+
def has_global_hosts(self) -> bool:
|
|
84
|
+
"""Check if there are any hosts in the configuration.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if at least one host entry exists, False otherwise
|
|
88
|
+
"""
|
|
89
|
+
targets = self.config_data.get("targets", [])
|
|
90
|
+
for t in targets:
|
|
91
|
+
if isinstance(t, dict):
|
|
92
|
+
for v in t.values():
|
|
93
|
+
if isinstance(v, list):
|
|
94
|
+
for item in v:
|
|
95
|
+
if isinstance(item, dict) and ("friendly" in item or "host" in item):
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def validate_config(self) -> bool:
|
|
101
|
+
"""Abstract method to validate the configuration.
|
|
102
|
+
|
|
103
|
+
Must be implemented by subclasses to provide specific validation logic.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if configuration is valid, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
pass
|
sshmenuc/core/config.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SSH configuration management.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
from .base import BaseSSHMenuC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConnectionManager(BaseSSHMenuC):
|
|
9
|
+
"""Manages SSH connection configurations.
|
|
10
|
+
|
|
11
|
+
Provides CRUD operations for targets and connections within the configuration.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config_file: Optional[str] = None):
|
|
15
|
+
super().__init__(config_file)
|
|
16
|
+
if config_file:
|
|
17
|
+
self.load_config()
|
|
18
|
+
|
|
19
|
+
def _get_target_key(self, target: Dict[str, Any]) -> str:
|
|
20
|
+
"""Extract the first (and only) key from a target dictionary.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
target: Target dictionary with single key
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The target key name
|
|
27
|
+
"""
|
|
28
|
+
return next(iter(target.keys()))
|
|
29
|
+
|
|
30
|
+
def _find_target(self, target_name: str) -> Optional[Dict[str, Any]]:
|
|
31
|
+
"""Find and return the target dictionary by name.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
target_name: Name of the target to find
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Target dictionary if found, None otherwise
|
|
38
|
+
"""
|
|
39
|
+
for target in self.config_data["targets"]:
|
|
40
|
+
if self._get_target_key(target) == target_name:
|
|
41
|
+
return target
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def validate_config(self) -> bool:
|
|
45
|
+
"""Validate the configuration structure.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if config has valid structure with 'targets' key, False otherwise
|
|
49
|
+
"""
|
|
50
|
+
if not isinstance(self.config_data, dict):
|
|
51
|
+
return False
|
|
52
|
+
if "targets" not in self.config_data:
|
|
53
|
+
return False
|
|
54
|
+
if not isinstance(self.config_data["targets"], list):
|
|
55
|
+
return False
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def create_target(self, target_name: str, connections: List[Dict[str, Any]]):
|
|
59
|
+
"""Create a new connection target.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
target_name: Name of the target to create
|
|
63
|
+
connections: List of connection configuration dictionaries
|
|
64
|
+
"""
|
|
65
|
+
target = {target_name: connections}
|
|
66
|
+
self.config_data["targets"].append(target)
|
|
67
|
+
|
|
68
|
+
def modify_target(self, target_name: str, new_target_name: str = None,
|
|
69
|
+
connections: List[Dict[str, Any]] = None):
|
|
70
|
+
"""Modify an existing target.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
target_name: Current name of the target
|
|
74
|
+
new_target_name: New name for the target (optional, if renaming)
|
|
75
|
+
connections: New connection list (optional, if updating connections)
|
|
76
|
+
"""
|
|
77
|
+
target = self._find_target(target_name)
|
|
78
|
+
if target:
|
|
79
|
+
if new_target_name:
|
|
80
|
+
target[new_target_name] = target.pop(target_name)
|
|
81
|
+
if connections:
|
|
82
|
+
key = self._get_target_key(target)
|
|
83
|
+
target[key] = connections
|
|
84
|
+
|
|
85
|
+
def delete_target(self, target_name: str):
|
|
86
|
+
"""Delete a target.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
target_name: Name of the target to delete
|
|
90
|
+
"""
|
|
91
|
+
self.config_data["targets"] = [
|
|
92
|
+
target for target in self.config_data["targets"]
|
|
93
|
+
if self._get_target_key(target) != target_name
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
def create_connection(self, target_name: str, friendly: str, host: str,
|
|
97
|
+
connection_type: str = "ssh", command: str = "ssh",
|
|
98
|
+
zone: str = "", project: str = ""):
|
|
99
|
+
"""Create a new connection within a target.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
target_name: Name of the target to add connection to
|
|
103
|
+
friendly: Friendly name for the connection
|
|
104
|
+
host: Host address to connect to
|
|
105
|
+
connection_type: Type of connection (ssh, gssh, docker)
|
|
106
|
+
command: Command to execute for connection
|
|
107
|
+
zone: Cloud zone (for gssh connections)
|
|
108
|
+
project: Cloud project (for gssh connections)
|
|
109
|
+
"""
|
|
110
|
+
connection = {
|
|
111
|
+
"friendly": friendly,
|
|
112
|
+
"host": host,
|
|
113
|
+
"connection_type": connection_type,
|
|
114
|
+
"command": command,
|
|
115
|
+
"zone": zone,
|
|
116
|
+
"project": project,
|
|
117
|
+
}
|
|
118
|
+
target = self._find_target(target_name)
|
|
119
|
+
if target:
|
|
120
|
+
target[target_name].append(connection)
|
|
121
|
+
|
|
122
|
+
def modify_connection(self, target_name: str, connection_index: int, **kwargs):
|
|
123
|
+
"""Modify an existing connection.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
target_name: Name of the target containing the connection
|
|
127
|
+
connection_index: Index of the connection to modify
|
|
128
|
+
**kwargs: Connection fields to update (host, user, certkey, etc.)
|
|
129
|
+
"""
|
|
130
|
+
target = self._find_target(target_name)
|
|
131
|
+
if target:
|
|
132
|
+
connection = target[target_name][connection_index]
|
|
133
|
+
for key, value in kwargs.items():
|
|
134
|
+
if value is not None:
|
|
135
|
+
connection[key] = value
|
|
136
|
+
|
|
137
|
+
def delete_connection(self, target_name: str, connection_index: int):
|
|
138
|
+
"""Delete a connection.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
target_name: Name of the target containing the connection
|
|
142
|
+
connection_index: Index of the connection to delete
|
|
143
|
+
"""
|
|
144
|
+
target = self._find_target(target_name)
|
|
145
|
+
if target:
|
|
146
|
+
target[target_name].pop(connection_index)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive configuration editor for managing targets and connections.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
from clint.textui import puts, colored
|
|
6
|
+
from .config import ConnectionManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigEditor:
|
|
10
|
+
"""Interactive editor for SSH configuration.
|
|
11
|
+
|
|
12
|
+
Provides forms and dialogs for creating, editing, and deleting
|
|
13
|
+
targets and connections within the configuration.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config_manager: ConnectionManager):
|
|
17
|
+
"""Initialize the config editor.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config_manager: ConnectionManager instance to modify
|
|
21
|
+
"""
|
|
22
|
+
self.manager = config_manager
|
|
23
|
+
|
|
24
|
+
def prompt_input(self, prompt: str, default: str = "") -> str:
|
|
25
|
+
"""Prompt user for input with optional default value.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
prompt: Prompt message to display
|
|
29
|
+
default: Default value if user presses enter
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
User input string
|
|
33
|
+
"""
|
|
34
|
+
if default:
|
|
35
|
+
result = input(f"{prompt} [{default}]: ").strip()
|
|
36
|
+
return result if result else default
|
|
37
|
+
return input(f"{prompt}: ").strip()
|
|
38
|
+
|
|
39
|
+
def confirm(self, message: str) -> bool:
|
|
40
|
+
"""Ask user for confirmation.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
message: Confirmation message
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
True if user confirms, False otherwise
|
|
47
|
+
"""
|
|
48
|
+
response = input(f"{message} [y/N]: ").strip().lower()
|
|
49
|
+
return response == 'y'
|
|
50
|
+
|
|
51
|
+
def add_target(self) -> bool:
|
|
52
|
+
"""Interactive form to add a new target.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if target was added, False if cancelled
|
|
56
|
+
"""
|
|
57
|
+
puts(colored.cyan("\n=== Add New Target ==="))
|
|
58
|
+
target_name = self.prompt_input("Target name (e.g., Production, Development)")
|
|
59
|
+
|
|
60
|
+
if not target_name:
|
|
61
|
+
puts(colored.red("Target name cannot be empty"))
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# Check if target already exists
|
|
65
|
+
if self.manager._find_target(target_name):
|
|
66
|
+
puts(colored.red(f"Target '{target_name}' already exists"))
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
self.manager.create_target(target_name, [])
|
|
70
|
+
self.manager.save_config()
|
|
71
|
+
puts(colored.green(f"✓ Target '{target_name}' created successfully"))
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def delete_target(self, target_name: str) -> bool:
|
|
75
|
+
"""Delete a target with confirmation.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
target_name: Name of target to delete
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if deleted, False if cancelled
|
|
82
|
+
"""
|
|
83
|
+
if not self.confirm(f"Delete target '{target_name}' and all its connections?"):
|
|
84
|
+
puts(colored.yellow("Cancelled"))
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
self.manager.delete_target(target_name)
|
|
88
|
+
self.manager.save_config()
|
|
89
|
+
puts(colored.green(f"✓ Target '{target_name}' deleted"))
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def rename_target(self, target_name: str) -> bool:
|
|
93
|
+
"""Rename a target.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
target_name: Current name of target
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
True if renamed, False if cancelled
|
|
100
|
+
"""
|
|
101
|
+
puts(colored.cyan(f"\n=== Rename Target '{target_name}' ==="))
|
|
102
|
+
new_name = self.prompt_input("New name", target_name)
|
|
103
|
+
|
|
104
|
+
if not new_name or new_name == target_name:
|
|
105
|
+
puts(colored.yellow("Cancelled"))
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
# Check if new name already exists
|
|
109
|
+
if self.manager._find_target(new_name):
|
|
110
|
+
puts(colored.red(f"Target '{new_name}' already exists"))
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
self.manager.modify_target(target_name, new_target_name=new_name)
|
|
114
|
+
self.manager.save_config()
|
|
115
|
+
puts(colored.green(f"✓ Target renamed to '{new_name}'"))
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
def add_connection(self, target_name: str) -> bool:
|
|
119
|
+
"""Interactive form to add a connection to a target.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
target_name: Target to add connection to
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
True if connection added, False if cancelled
|
|
126
|
+
"""
|
|
127
|
+
puts(colored.cyan(f"\n=== Add Connection to '{target_name}' ==="))
|
|
128
|
+
|
|
129
|
+
friendly = self.prompt_input("Friendly name (display name)")
|
|
130
|
+
if not friendly:
|
|
131
|
+
puts(colored.red("Friendly name cannot be empty"))
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
host = self.prompt_input("Host (IP or hostname)")
|
|
135
|
+
if not host:
|
|
136
|
+
puts(colored.red("Host cannot be empty"))
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
user = self.prompt_input("Username (optional, leave empty for current user)", "")
|
|
140
|
+
certkey = self.prompt_input("SSH key path (optional)", "")
|
|
141
|
+
connection_type = self.prompt_input("Connection type", "ssh")
|
|
142
|
+
|
|
143
|
+
# Build connection dict with only non-empty fields
|
|
144
|
+
connection = {
|
|
145
|
+
"friendly": friendly,
|
|
146
|
+
"host": host,
|
|
147
|
+
"connection_type": connection_type,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if user:
|
|
151
|
+
connection["user"] = user
|
|
152
|
+
if certkey:
|
|
153
|
+
connection["certkey"] = certkey
|
|
154
|
+
|
|
155
|
+
# Add connection using the existing method
|
|
156
|
+
target = self.manager._find_target(target_name)
|
|
157
|
+
if target:
|
|
158
|
+
target[target_name].append(connection)
|
|
159
|
+
self.manager.save_config()
|
|
160
|
+
puts(colored.green(f"✓ Connection '{friendly}' added to '{target_name}'"))
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
puts(colored.red(f"Target '{target_name}' not found"))
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
def edit_connection(self, target_name: str, connection_index: int,
|
|
167
|
+
connection: Dict[str, Any]) -> bool:
|
|
168
|
+
"""Interactive form to edit a connection.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
target_name: Target containing the connection
|
|
172
|
+
connection_index: Index of connection to edit
|
|
173
|
+
connection: Current connection data
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if edited, False if cancelled
|
|
177
|
+
"""
|
|
178
|
+
puts(colored.cyan(f"\n=== Edit Connection '{connection.get('friendly', 'Unknown')}' ==="))
|
|
179
|
+
|
|
180
|
+
friendly = self.prompt_input("Friendly name", connection.get("friendly", ""))
|
|
181
|
+
host = self.prompt_input("Host", connection.get("host", ""))
|
|
182
|
+
user = self.prompt_input("Username", connection.get("user", ""))
|
|
183
|
+
certkey = self.prompt_input("SSH key path", connection.get("certkey", ""))
|
|
184
|
+
|
|
185
|
+
if not friendly or not host:
|
|
186
|
+
puts(colored.red("Friendly name and host cannot be empty"))
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
self.manager.modify_connection(
|
|
190
|
+
target_name, connection_index,
|
|
191
|
+
friendly=friendly, host=host, user=user or None, certkey=certkey or None
|
|
192
|
+
)
|
|
193
|
+
self.manager.save_config()
|
|
194
|
+
puts(colored.green(f"✓ Connection updated"))
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
def delete_connection(self, target_name: str, connection_index: int,
|
|
198
|
+
connection: Dict[str, Any]) -> bool:
|
|
199
|
+
"""Delete a connection with confirmation.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
target_name: Target containing the connection
|
|
203
|
+
connection_index: Index of connection to delete
|
|
204
|
+
connection: Connection data (for display)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if deleted, False if cancelled
|
|
208
|
+
"""
|
|
209
|
+
friendly = connection.get("friendly", "Unknown")
|
|
210
|
+
if not self.confirm(f"Delete connection '{friendly}'?"):
|
|
211
|
+
puts(colored.yellow("Cancelled"))
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
self.manager.delete_connection(target_name, connection_index)
|
|
215
|
+
self.manager.save_config()
|
|
216
|
+
puts(colored.green(f"✓ Connection '{friendly}' deleted"))
|
|
217
|
+
return True
|