ntermqt 0.1.7__py3-none-any.whl → 0.1.9__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.
@@ -0,0 +1,195 @@
1
+ """
2
+ nterm/scripting/models.py
3
+
4
+ Data models for the scripting API.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ import paramiko
9
+ from typing import Optional, List, Dict, Any
10
+ from dataclasses import dataclass, asdict, field
11
+ from datetime import datetime
12
+
13
+ from ..manager.models import SavedSession
14
+
15
+
16
+ @dataclass
17
+ class ActiveSession:
18
+ """Represents an active SSH connection to a device."""
19
+ device_name: str
20
+ hostname: str
21
+ port: int
22
+ platform: Optional[str] = None
23
+ client: Optional[paramiko.SSHClient] = None
24
+ shell: Optional[paramiko.Channel] = None
25
+ prompt: Optional[str] = None
26
+ connected_at: datetime = field(default_factory=datetime.now)
27
+
28
+ def is_connected(self) -> bool:
29
+ """Check if session is still active."""
30
+ return self.client is not None and self.shell is not None and self.shell.active
31
+
32
+ def __repr__(self) -> str:
33
+ status = "connected" if self.is_connected() else "disconnected"
34
+ platform = f", platform={self.platform}" if self.platform else ""
35
+ return f"<ActiveSession {self.device_name}@{self.hostname}:{self.port} {status}{platform}>"
36
+
37
+ def __str__(self) -> str:
38
+ """Detailed string representation."""
39
+ lines = [
40
+ f"Active Session: {self.device_name}",
41
+ f" Host: {self.hostname}:{self.port}",
42
+ f" Status: {'connected' if self.is_connected() else 'disconnected'}",
43
+ ]
44
+ if self.platform:
45
+ lines.append(f" Platform: {self.platform}")
46
+ if self.prompt:
47
+ lines.append(f" Prompt: {self.prompt}")
48
+ lines.append(f" Connected at: {self.connected_at.strftime('%Y-%m-%d %H:%M:%S')}")
49
+ return '\n'.join(lines)
50
+
51
+
52
+ @dataclass
53
+ class CommandResult:
54
+ """Result of executing a command on a device."""
55
+ command: str
56
+ raw_output: str
57
+ platform: Optional[str] = None
58
+ parsed_data: Optional[List[Dict[str, Any]]] = None
59
+ parse_success: bool = False
60
+ parse_template: Optional[str] = None
61
+ normalized_fields: Optional[Dict[str, str]] = None
62
+ timestamp: datetime = field(default_factory=datetime.now)
63
+
64
+ def to_dict(self) -> Dict[str, Any]:
65
+ """Convert to dictionary."""
66
+ return {
67
+ 'command': self.command,
68
+ 'raw_output': self.raw_output,
69
+ 'platform': self.platform,
70
+ 'parsed_data': self.parsed_data,
71
+ 'parse_success': self.parse_success,
72
+ 'parse_template': self.parse_template,
73
+ 'normalized_fields': self.normalized_fields,
74
+ 'timestamp': self.timestamp.isoformat(),
75
+ }
76
+
77
+ def __repr__(self) -> str:
78
+ parsed = f", {len(self.parsed_data)} parsed" if self.parsed_data else ""
79
+ platform = f", platform={self.platform}" if self.platform else ""
80
+ return f"<CommandResult '{self.command}'{platform}{parsed}>"
81
+
82
+ def __str__(self) -> str:
83
+ """Detailed string representation."""
84
+ lines = [
85
+ f"Command: {self.command}",
86
+ f"Timestamp: {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}",
87
+ ]
88
+ if self.platform:
89
+ lines.append(f"Platform: {self.platform}")
90
+
91
+ lines.append(f"Parse success: {self.parse_success}")
92
+ if self.parse_template:
93
+ lines.append(f"Template: {self.parse_template}")
94
+
95
+ if self.parsed_data:
96
+ lines.append(f"Parsed rows: {len(self.parsed_data)}")
97
+ if self.normalized_fields:
98
+ lines.append(f"Field normalization: {self.normalized_fields['map_used']}")
99
+
100
+ lines.append(f"\nRaw output ({len(self.raw_output)} chars):")
101
+ lines.append("-" * 60)
102
+ lines.append(self.raw_output[:500] + ("..." if len(self.raw_output) > 500 else ""))
103
+
104
+ return '\n'.join(lines)
105
+
106
+
107
+ @dataclass
108
+ class DeviceInfo:
109
+ """Simplified device view for scripting."""
110
+ name: str
111
+ hostname: str
112
+ port: int
113
+ folder: Optional[str] = None
114
+ credential: Optional[str] = None
115
+ last_connected: Optional[str] = None
116
+ connect_count: int = 0
117
+
118
+ @classmethod
119
+ def from_session(cls, session: SavedSession, folder_name: str = None) -> 'DeviceInfo':
120
+ return cls(
121
+ name=session.name,
122
+ hostname=session.hostname,
123
+ port=session.port,
124
+ folder=folder_name,
125
+ credential=session.credential_name,
126
+ last_connected=str(session.last_connected) if session.last_connected else None,
127
+ connect_count=session.connect_count,
128
+ )
129
+
130
+ def to_dict(self) -> Dict[str, Any]:
131
+ return asdict(self)
132
+
133
+ def __repr__(self) -> str:
134
+ cred = f", cred={self.credential}" if self.credential else ""
135
+ folder = f", folder={self.folder}" if self.folder else ""
136
+ return f"Device({self.name}, {self.hostname}:{self.port}{cred}{folder})"
137
+
138
+ def __str__(self) -> str:
139
+ """Detailed string representation."""
140
+ lines = [
141
+ f"Device: {self.name}",
142
+ f" Hostname: {self.hostname}:{self.port}",
143
+ ]
144
+ if self.folder:
145
+ lines.append(f" Folder: {self.folder}")
146
+ if self.credential:
147
+ lines.append(f" Credential: {self.credential}")
148
+ if self.last_connected:
149
+ lines.append(f" Last connected: {self.last_connected}")
150
+ if self.connect_count > 0:
151
+ lines.append(f" Connection count: {self.connect_count}")
152
+ return '\n'.join(lines)
153
+
154
+
155
+ @dataclass
156
+ class CredentialInfo:
157
+ """Simplified credential view for scripting (no secrets exposed)."""
158
+ name: str
159
+ username: str
160
+ has_password: bool
161
+ has_key: bool
162
+ match_hosts: List[str]
163
+ match_tags: List[str]
164
+ jump_host: Optional[str] = None
165
+ is_default: bool = False
166
+
167
+ def to_dict(self) -> Dict[str, Any]:
168
+ return asdict(self)
169
+
170
+ def __repr__(self) -> str:
171
+ auth = []
172
+ if self.has_password:
173
+ auth.append("password")
174
+ if self.has_key:
175
+ auth.append("key")
176
+ auth_str = "+".join(auth) if auth else "none"
177
+ default = " [default]" if self.is_default else ""
178
+ return f"Credential({self.name}, user={self.username}, auth={auth_str}{default})"
179
+
180
+ def __str__(self) -> str:
181
+ """Detailed string representation."""
182
+ lines = [
183
+ f"Credential: {self.name}",
184
+ f" Username: {self.username}",
185
+ f" Authentication: {'password' if self.has_password else ''}{'+' if self.has_password and self.has_key else ''}{'SSH key' if self.has_key else ''}",
186
+ ]
187
+ if self.match_hosts:
188
+ lines.append(f" Host patterns: {', '.join(self.match_hosts)}")
189
+ if self.match_tags:
190
+ lines.append(f" Tags: {', '.join(self.match_tags)}")
191
+ if self.jump_host:
192
+ lines.append(f" Jump host: {self.jump_host}")
193
+ if self.is_default:
194
+ lines.append(f" [DEFAULT]")
195
+ return '\n'.join(lines)
@@ -0,0 +1,272 @@
1
+ """
2
+ nterm/scripting/platform_data.py
3
+
4
+ Platform configuration constants for device detection and command mapping.
5
+ """
6
+
7
+ # =============================================================================
8
+ # Platform Detection Patterns
9
+ # =============================================================================
10
+
11
+ PLATFORM_PATTERNS = {
12
+ 'arista_eos': [
13
+ r'Arista',
14
+ r'vEOS',
15
+ ],
16
+ 'cisco_ios': [
17
+ r'Cisco IOS Software',
18
+ r'IOS \(tm\)',
19
+ ],
20
+ 'cisco_nxos': [
21
+ r'Cisco Nexus',
22
+ r'NX-OS',
23
+ ],
24
+ 'cisco_iosxe': [
25
+ r'Cisco IOS XE Software',
26
+ ],
27
+ 'cisco_iosxr': [
28
+ r'Cisco IOS XR Software',
29
+ ],
30
+ 'juniper_junos': [
31
+ r'JUNOS',
32
+ r'Juniper Networks',
33
+ ],
34
+ }
35
+
36
+
37
+ # =============================================================================
38
+ # Platform-specific Command Mappings
39
+ # =============================================================================
40
+
41
+ PLATFORM_COMMANDS = {
42
+ 'arista_eos': {
43
+ 'config': 'show running-config',
44
+ 'version': 'show version',
45
+ 'interfaces': 'show interfaces',
46
+ 'interfaces_status': 'show interfaces status',
47
+ 'interface_detail': 'show interfaces {name}',
48
+ 'neighbors_cdp': None, # Arista doesn't support CDP by default
49
+ 'neighbors_lldp': 'show lldp neighbors detail',
50
+ 'neighbors': 'show lldp neighbors detail', # Preferred protocol
51
+ 'routing_table': 'show ip route',
52
+ 'bgp_summary': 'show ip bgp summary',
53
+ 'bgp_neighbors': 'show ip bgp neighbors',
54
+ },
55
+ 'cisco_ios': {
56
+ 'config': 'show running-config',
57
+ 'version': 'show version',
58
+ 'interfaces': 'show interfaces',
59
+ 'interfaces_status': 'show interfaces status',
60
+ 'interface_detail': 'show interfaces {name}',
61
+ 'neighbors_cdp': 'show cdp neighbors detail',
62
+ 'neighbors_lldp': 'show lldp neighbors detail',
63
+ 'neighbors': 'show cdp neighbors detail', # Preferred protocol
64
+ 'routing_table': 'show ip route',
65
+ 'bgp_summary': 'show ip bgp summary',
66
+ 'bgp_neighbors': 'show ip bgp neighbors',
67
+ },
68
+ 'cisco_iosxe': {
69
+ 'config': 'show running-config',
70
+ 'version': 'show version',
71
+ 'interfaces': 'show interfaces',
72
+ 'interfaces_status': 'show interfaces status',
73
+ 'interface_detail': 'show interfaces {name}',
74
+ 'neighbors_cdp': 'show cdp neighbors detail',
75
+ 'neighbors_lldp': 'show lldp neighbors detail',
76
+ 'neighbors': 'show cdp neighbors detail',
77
+ 'routing_table': 'show ip route',
78
+ 'bgp_summary': 'show ip bgp summary',
79
+ 'bgp_neighbors': 'show ip bgp neighbors',
80
+ },
81
+ 'cisco_nxos': {
82
+ 'config': 'show running-config',
83
+ 'version': 'show version',
84
+ 'interfaces': 'show interface',
85
+ 'interfaces_status': 'show interface status',
86
+ 'interface_detail': 'show interface {name}',
87
+ 'neighbors_cdp': 'show cdp neighbors detail',
88
+ 'neighbors_lldp': 'show lldp neighbors detail',
89
+ 'neighbors': 'show cdp neighbors detail',
90
+ 'routing_table': 'show ip route',
91
+ 'bgp_summary': 'show ip bgp summary',
92
+ 'bgp_neighbors': 'show ip bgp neighbors',
93
+ },
94
+ 'cisco_iosxr': {
95
+ 'config': 'show running-config',
96
+ 'version': 'show version',
97
+ 'interfaces': 'show interfaces',
98
+ 'interfaces_status': 'show interfaces summary',
99
+ 'interface_detail': 'show interfaces {name}',
100
+ 'neighbors_cdp': 'show cdp neighbors detail',
101
+ 'neighbors_lldp': 'show lldp neighbors detail',
102
+ 'neighbors': 'show cdp neighbors detail',
103
+ 'routing_table': 'show route',
104
+ 'bgp_summary': 'show bgp summary',
105
+ 'bgp_neighbors': 'show bgp neighbors',
106
+ },
107
+ 'juniper_junos': {
108
+ 'config': 'show configuration',
109
+ 'version': 'show version',
110
+ 'interfaces': 'show interfaces',
111
+ 'interfaces_status': 'show interfaces terse',
112
+ 'interface_detail': 'show interfaces {name} extensive',
113
+ 'neighbors_cdp': None, # Juniper doesn't support CDP
114
+ 'neighbors_lldp': 'show lldp neighbors',
115
+ 'neighbors': 'show lldp neighbors',
116
+ 'routing_table': 'show route',
117
+ 'bgp_summary': 'show bgp summary',
118
+ 'bgp_neighbors': 'show bgp neighbor',
119
+ },
120
+ }
121
+
122
+ DEFAULT_COMMANDS = {
123
+ 'config': 'show running-config',
124
+ 'version': 'show version',
125
+ 'interfaces': 'show interfaces',
126
+ 'interfaces_status': 'show interfaces status',
127
+ 'interface_detail': 'show interfaces {name}',
128
+ 'neighbors_cdp': 'show cdp neighbors detail',
129
+ 'neighbors_lldp': 'show lldp neighbors detail',
130
+ 'neighbors': 'show cdp neighbors detail',
131
+ 'routing_table': 'show ip route',
132
+ 'bgp_summary': 'show ip bgp summary',
133
+ 'bgp_neighbors': 'show ip bgp neighbors',
134
+ }
135
+
136
+
137
+ # =============================================================================
138
+ # Vendor Field Mappings for Output Normalization
139
+ # =============================================================================
140
+
141
+ # Maps canonical field names to vendor-specific template field names
142
+ INTERFACE_DETAIL_FIELD_MAP = {
143
+ 'arista_eos': {
144
+ 'interface': ['INTERFACE'],
145
+ 'admin_state': ['LINK_STATUS'],
146
+ 'oper_state': ['PROTOCOL_STATUS'],
147
+ 'hardware': ['HARDWARE_TYPE'],
148
+ 'mac_address': ['MAC_ADDRESS'],
149
+ 'description': ['DESCRIPTION'],
150
+ 'mtu': ['MTU'],
151
+ 'bandwidth': ['BANDWIDTH'],
152
+ 'in_packets': ['INPUT_PACKETS'],
153
+ 'out_packets': ['OUTPUT_PACKETS'],
154
+ 'in_errors': ['INPUT_ERRORS'],
155
+ 'out_errors': ['OUTPUT_ERRORS'],
156
+ 'crc_errors': ['CRC'],
157
+ },
158
+ 'cisco_ios': {
159
+ 'interface': ['INTERFACE'],
160
+ 'admin_state': ['LINK_STATUS'],
161
+ 'oper_state': ['PROTOCOL_STATUS'],
162
+ 'hardware': ['HARDWARE_TYPE'],
163
+ 'mac_address': ['MAC_ADDRESS'],
164
+ 'description': ['DESCRIPTION'],
165
+ 'mtu': ['MTU'],
166
+ 'bandwidth': ['BANDWIDTH'],
167
+ 'duplex': ['DUPLEX'],
168
+ 'speed': ['SPEED'],
169
+ 'in_packets': ['INPUT_PACKETS'],
170
+ 'out_packets': ['OUTPUT_PACKETS'],
171
+ 'in_errors': ['INPUT_ERRORS'],
172
+ 'out_errors': ['OUTPUT_ERRORS'],
173
+ 'crc_errors': ['CRC'],
174
+ },
175
+ 'cisco_nxos': {
176
+ 'interface': ['INTERFACE'],
177
+ 'admin_state': ['ADMIN_STATE', 'LINK_STATUS'],
178
+ 'oper_state': ['OPER_STATE', 'PROTOCOL_STATUS'],
179
+ 'hardware': ['HARDWARE_TYPE'],
180
+ 'mac_address': ['MAC_ADDRESS', 'ADDRESS'],
181
+ 'description': ['DESCRIPTION'],
182
+ 'mtu': ['MTU'],
183
+ 'bandwidth': ['BANDWIDTH', 'BW'],
184
+ 'in_packets': ['IN_PKTS', 'INPUT_PACKETS'],
185
+ 'out_packets': ['OUT_PKTS', 'OUTPUT_PACKETS'],
186
+ 'in_errors': ['IN_ERRORS', 'INPUT_ERRORS'],
187
+ 'out_errors': ['OUT_ERRORS', 'OUTPUT_ERRORS'],
188
+ 'crc_errors': ['CRC', 'CRC_ERRORS'],
189
+ },
190
+ 'juniper_junos': {
191
+ 'interface': ['INTERFACE'],
192
+ 'admin_state': ['ADMIN_STATE'],
193
+ 'oper_state': ['LINK_STATUS'],
194
+ 'hardware': ['HARDWARE_TYPE'],
195
+ 'mac_address': ['MAC_ADDRESS'],
196
+ 'description': ['DESCRIPTION'],
197
+ 'mtu': ['MTU'],
198
+ 'bandwidth': ['BANDWIDTH'],
199
+ 'in_packets': ['INPUT_PACKETS'],
200
+ 'out_packets': ['OUTPUT_PACKETS'],
201
+ 'in_errors': ['INPUT_ERRORS'],
202
+ 'out_errors': ['OUTPUT_ERRORS'],
203
+ 'crc_errors': ['CRC'],
204
+ },
205
+ }
206
+
207
+ DEFAULT_FIELD_MAP = {
208
+ 'interface': ['INTERFACE', 'PORT', 'NAME'],
209
+ 'admin_state': ['ADMIN_STATE', 'LINK_STATUS', 'STATUS'],
210
+ 'oper_state': ['OPER_STATE', 'PROTOCOL_STATUS', 'LINE_STATUS'],
211
+ 'hardware': ['HARDWARE_TYPE', 'HARDWARE', 'MEDIA_TYPE', 'TYPE'],
212
+ 'mac_address': ['MAC_ADDRESS', 'ADDRESS', 'MAC'],
213
+ 'description': ['DESCRIPTION', 'NAME', 'DESC'],
214
+ 'mtu': ['MTU'],
215
+ 'bandwidth': ['BANDWIDTH', 'BW', 'SPEED'],
216
+ 'duplex': ['DUPLEX'],
217
+ 'speed': ['SPEED'],
218
+ 'in_packets': ['INPUT_PACKETS', 'IN_PKTS', 'IN_PACKETS'],
219
+ 'out_packets': ['OUTPUT_PACKETS', 'OUT_PKTS', 'OUT_PACKETS'],
220
+ 'in_errors': ['INPUT_ERRORS', 'IN_ERRORS'],
221
+ 'out_errors': ['OUTPUT_ERRORS', 'OUT_ERRORS'],
222
+ 'crc_errors': ['CRC', 'CRC_ERRORS'],
223
+ }
224
+
225
+ # Version output field mappings
226
+ VERSION_FIELD_MAP = {
227
+ 'arista_eos': {
228
+ 'version': ['VERSION', 'version'],
229
+ 'hardware': ['HARDWARE', 'hardware', 'MODEL', 'model'],
230
+ 'serial': ['SERIAL', 'serial', 'SERIAL_NUMBER'],
231
+ 'uptime': ['UPTIME', 'uptime'],
232
+ 'hostname': ['HOSTNAME', 'hostname'],
233
+ },
234
+ 'cisco_ios': {
235
+ 'version': ['VERSION', 'version', 'SOFTWARE', 'software'],
236
+ 'hardware': ['HARDWARE', 'hardware', 'MODEL', 'model', 'PLATFORM'],
237
+ 'serial': ['SERIAL', 'serial', 'SERIAL_NUMBER', 'serial_number'],
238
+ 'uptime': ['UPTIME', 'uptime'],
239
+ 'hostname': ['HOSTNAME', 'hostname'],
240
+ },
241
+ 'cisco_nxos': {
242
+ 'version': ['VERSION', 'version', 'OS', 'os'],
243
+ 'hardware': ['HARDWARE', 'hardware', 'PLATFORM', 'platform'],
244
+ 'serial': ['SERIAL', 'serial'],
245
+ 'uptime': ['UPTIME', 'uptime'],
246
+ 'hostname': ['HOSTNAME', 'hostname', 'DEVICE_NAME'],
247
+ },
248
+ 'juniper_junos': {
249
+ 'version': ['VERSION', 'version', 'JUNOS'],
250
+ 'hardware': ['HARDWARE', 'hardware', 'MODEL', 'model'],
251
+ 'serial': ['SERIAL', 'serial', 'SERIAL_NUMBER'],
252
+ 'uptime': ['UPTIME', 'uptime'],
253
+ 'hostname': ['HOSTNAME', 'hostname'],
254
+ },
255
+ }
256
+
257
+ DEFAULT_VERSION_FIELD_MAP = {
258
+ 'version': ['VERSION', 'version', 'SOFTWARE', 'software', 'OS'],
259
+ 'hardware': ['HARDWARE', 'hardware', 'MODEL', 'model', 'PLATFORM', 'platform'],
260
+ 'serial': ['SERIAL', 'serial', 'SERIAL_NUMBER', 'serial_number'],
261
+ 'uptime': ['UPTIME', 'uptime'],
262
+ 'hostname': ['HOSTNAME', 'hostname', 'DEVICE_NAME'],
263
+ }
264
+
265
+ # Neighbor (CDP/LLDP) field mappings
266
+ NEIGHBOR_FIELD_MAP = {
267
+ 'local_interface': ['LOCAL_INTERFACE', 'local_port', 'LOCAL_PORT'],
268
+ 'neighbor_device': ['NEIGHBOR_NAME', 'DESTINATION_HOST', 'SYSTEM_NAME', 'neighbor', 'NEIGHBOR'],
269
+ 'neighbor_interface': ['NEIGHBOR_INTERFACE', 'remote_port', 'REMOTE_PORT', 'PORT_ID'],
270
+ 'platform': ['PLATFORM', 'platform', 'SYSTEM_DESCRIPTION'],
271
+ 'ip_address': ['MANAGEMENT_IP', 'IP_ADDRESS', 'ip_address'],
272
+ }