netbox-toolkit-plugin 0.1.0__py3-none-any.whl → 0.1.2__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.
- netbox_toolkit_plugin/__init__.py +32 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/serializers.py +71 -35
- {netbox_toolkit → netbox_toolkit_plugin}/api/urls.py +3 -3
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/factory.py +170 -111
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/netmiko_connector.py +242 -179
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/scrapli_connector.py +256 -172
- netbox_toolkit_plugin/migrations/0001_initial.py +108 -0
- netbox_toolkit_plugin/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +70 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0003_permission_system_update.py +26 -12
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0004_remove_django_permissions.py +27 -29
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0005_alter_command_options_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0007_alter_commandlog_parsing_template.py +6 -4
- {netbox_toolkit → netbox_toolkit_plugin}/models.py +31 -32
- {netbox_toolkit → netbox_toolkit_plugin}/navigation.py +6 -6
- {netbox_toolkit → netbox_toolkit_plugin}/services/command_service.py +188 -128
- {netbox_toolkit → netbox_toolkit_plugin}/services/rate_limiting_service.py +104 -97
- netbox_toolkit_plugin/settings.py +176 -0
- netbox_toolkit_plugin/tables.py +51 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command.html +108 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command_list.html +12 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/commandlog.html +170 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/device_toolkit.html +557 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command.html +5 -5
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_list.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/device_toolkit.html +6 -6
- netbox_toolkit_plugin/urls.py +38 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/logging.py +20 -19
- {netbox_toolkit → netbox_toolkit_plugin}/views.py +251 -169
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/METADATA +2 -2
- netbox_toolkit_plugin-0.1.2.dist-info/RECORD +60 -0
- netbox_toolkit_plugin-0.1.2.dist-info/entry_points.txt +2 -0
- netbox_toolkit_plugin-0.1.2.dist-info/top_level.txt +1 -0
- netbox_toolkit/__init__.py +0 -30
- netbox_toolkit/config.py +0 -159
- netbox_toolkit/migrations/0001_initial.py +0 -54
- netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +0 -66
- netbox_toolkit/tables.py +0 -37
- netbox_toolkit/urls.py +0 -22
- netbox_toolkit_plugin-0.1.0.dist-info/RECORD +0 -56
- netbox_toolkit_plugin-0.1.0.dist-info/entry_points.txt +0 -2
- netbox_toolkit_plugin-0.1.0.dist-info/top_level.txt +0 -1
- {netbox_toolkit → netbox_toolkit_plugin}/admin.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/mixins.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/schemas.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/command_logs.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/commands.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/base.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/exceptions.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/filtersets.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/forms.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/search.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/device_service.py +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/css/toolkit.css +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/js/toolkit.js +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_edit.html +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog_list.html +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/connection.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/error_parser.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/network.py +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/WHEEL +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
netbox_toolkit_plugin/__init__.py,sha256=3QswdYHYy_jIlALcrIFznVfnJ0g_-OzQ9B2ACQagKh4,848
|
2
|
+
netbox_toolkit_plugin/admin.py,sha256=j0O2hke80XbGCfU3lkxl8__tRmBArNLoQtLyA39hepI,690
|
3
|
+
netbox_toolkit_plugin/exceptions.py,sha256=qh7SxXLTqwsduxUMiTA_pXQ67VHSpxRuQgdlfXMl6Bk,785
|
4
|
+
netbox_toolkit_plugin/filtersets.py,sha256=ppmnfQ8IUHm60BiJR9_cdn1ygvuFlMfj6YIFLJ-XeMc,2722
|
5
|
+
netbox_toolkit_plugin/forms.py,sha256=6aUaXuJtx3ZL2iEbIq2zXuiXCu2bAHDUMI2EJJBPsWg,1037
|
6
|
+
netbox_toolkit_plugin/models.py,sha256=-TyH0xJrHxqfM1XC5pAFesQF38oec6JcAJs81Ay6arE,2761
|
7
|
+
netbox_toolkit_plugin/navigation.py,sha256=SvJRHkvlFgG42sLwxCTG6-6X976IgXTKhiRCPe8ZG6U,965
|
8
|
+
netbox_toolkit_plugin/search.py,sha256=v8HPgp_0Nq-s_IoV7n4Kfi63_u535jVAoT-31ntdZlA,564
|
9
|
+
netbox_toolkit_plugin/settings.py,sha256=9Rc1kIPNbnJYEOpc1UQGULbIZUTN5VE7aFmeZyXPWo0,5797
|
10
|
+
netbox_toolkit_plugin/tables.py,sha256=kR90aUXuJxKiSNz7Mm62ZanodDBtExDj7AgROkh1rJw,1417
|
11
|
+
netbox_toolkit_plugin/urls.py,sha256=CbwZvFreUifZUFZUQrC-KyqVte_INKzMhnSIgzAFUVY,1215
|
12
|
+
netbox_toolkit_plugin/views.py,sha256=mmcxl70hgZ-W1R9gozXneeczcyP33InldLZRW1_p-yA,18782
|
13
|
+
netbox_toolkit_plugin/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
netbox_toolkit_plugin/api/mixins.py,sha256=Yxog3LQQqmzIiXjxZ5VxSbwV3er-Pxs2GpPEJUipvfY,2241
|
15
|
+
netbox_toolkit_plugin/api/schemas.py,sha256=Xza5-H2ZqBPCEPyMzDkbmSofY9nE73epu0PKvCB1_Ys,7334
|
16
|
+
netbox_toolkit_plugin/api/serializers.py,sha256=lPQLWffuRScwrmiMS24ejmwnaXqBCzlUNG92KW3dBB8,6123
|
17
|
+
netbox_toolkit_plugin/api/urls.py,sha256=_shol27G-7MlflSfLmfznLuw4xhZtrc0VBFQLxda-vE,280
|
18
|
+
netbox_toolkit_plugin/api/views/__init__.py,sha256=CksHVk9Jj81kImVlr_JnP9VGwFCAhtrnRzqw7H7pjcI,189
|
19
|
+
netbox_toolkit_plugin/api/views/command_logs.py,sha256=4PdUQPvle9R9pJcJalYS4MCKYhmG_5EbAUowbjVetgw,6255
|
20
|
+
netbox_toolkit_plugin/api/views/commands.py,sha256=dv6sjN3L0FTyAWmM4u7zZBPOug6H_WcIR6egd3XE3A4,10939
|
21
|
+
netbox_toolkit_plugin/connectors/__init__.py,sha256=lL1WCo-rlcjNGVwmwSN1PT592xr5LEPT8k5zEVZlltE,419
|
22
|
+
netbox_toolkit_plugin/connectors/base.py,sha256=Z88tVG87H8IXdub55t-_uu1ptdhKo0uaz9l9iO-Xi0I,2829
|
23
|
+
netbox_toolkit_plugin/connectors/factory.py,sha256=z_XULU26VuyfdwkqyFMvmTFqrPVDSmmmmlt5Slk0-lU,13722
|
24
|
+
netbox_toolkit_plugin/connectors/netmiko_connector.py,sha256=k2bNuKMCJjb1F8lXR05v19GMFURDqG6SammlIKBz_Kk,18965
|
25
|
+
netbox_toolkit_plugin/connectors/scrapli_connector.py,sha256=O3iuVczGophquhA5-QECZPa68rLp2DDh5Jk2JXKEk1M,23369
|
26
|
+
netbox_toolkit_plugin/migrations/0001_initial.py,sha256=4fycU83wXx-sN73YTxAZekRDx9uVxzHg38Dl-G6EqxY,3729
|
27
|
+
netbox_toolkit_plugin/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py,sha256=FrGCbl5BnnWzqjLsMZtykgnAjzpI0j6A-6SQxKwGiAM,2174
|
28
|
+
netbox_toolkit_plugin/migrations/0003_permission_system_update.py,sha256=BUBDkAnn04Snqj-EZdi0vfCIXjbakUMlmnCawUPwpbI,1965
|
29
|
+
netbox_toolkit_plugin/migrations/0004_remove_django_permissions.py,sha256=hj6UrpfmEk4gnEVziGJHFCPvvZfDlUFH1GvxQ4Aw-h8,2545
|
30
|
+
netbox_toolkit_plugin/migrations/0005_alter_command_options_and_more.py,sha256=TOiyo2QKporoA089Vjri5rSFQX3BH1IxY1hj1voN7Rs,609
|
31
|
+
netbox_toolkit_plugin/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py,sha256=cMIeOi0j3gXfwUYO3hQ4Kr5Uok6cslyUCEurtgKN2hQ,771
|
32
|
+
netbox_toolkit_plugin/migrations/0007_alter_commandlog_parsing_template.py,sha256=VedbEIA-54JL_2I-MC1YPqppKS8WdpedB1nbFIkBcLU,512
|
33
|
+
netbox_toolkit_plugin/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
|
+
netbox_toolkit_plugin/services/__init__.py,sha256=nzdv0yOAyoAgm3ReKn-zLCbxkzEZfDpfnVDy9UExtyg,273
|
35
|
+
netbox_toolkit_plugin/services/command_service.py,sha256=e-PmGelmwZnTgxBf6L8-aditnj_10RlNVL8qjQVpkPo,15991
|
36
|
+
netbox_toolkit_plugin/services/device_service.py,sha256=SWqWw7c5cBhbXE_jCJwfgc3KvxchQq_6NeS2hlb3NDA,2945
|
37
|
+
netbox_toolkit_plugin/services/rate_limiting_service.py,sha256=prru5A8TZ9kVGsapNoxYxFyPONaHp2zmaVn1clFXBCU,8574
|
38
|
+
netbox_toolkit_plugin/static/netbox_toolkit_plugin/css/toolkit.css,sha256=VmyM2_rBJhiP43whtMFaYYP4EW4MUGHPPr84mOo_0Qg,4236
|
39
|
+
netbox_toolkit_plugin/static/netbox_toolkit_plugin/js/toolkit.js,sha256=0oF2p8g1Yu28hKQswj1-2Kctt_09YtT__Xeo39LfQ-I,25721
|
40
|
+
netbox_toolkit_plugin/templates/netbox_toolkit/command.html,sha256=Aol4n0LVRO_yuHf0iUnxkQlPSMOW-V_dT_rd89rZTDE,3191
|
41
|
+
netbox_toolkit_plugin/templates/netbox_toolkit/command_list.html,sha256=hFnVL58CNDce06ICITBjdEY8w_lNYxjb9_B24PRSTc0,365
|
42
|
+
netbox_toolkit_plugin/templates/netbox_toolkit/commandlog.html,sha256=u_vNBBWmYT4ln2Qz6DylkDSLNm9h4n2j1dYhFWgOk8I,5196
|
43
|
+
netbox_toolkit_plugin/templates/netbox_toolkit/device_toolkit.html,sha256=4muIExwJ6DU3IcmDexjpOxZ8hNnIQzfiTd-17rXa9A0,30731
|
44
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/command.html,sha256=WmRepyhomJZeFAuAGQ8x0K1EFmR8vh7htQBcw7FPv8I,3422
|
45
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/command_edit.html,sha256=Dx3QVrtLegViZl9_VWeVayZ1DIgtzWDnrmgS5_TfOCg,175
|
46
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/command_list.html,sha256=zPxg_c1cfNgF54u0nj03DeglXS8N6mSJIx11OXO8fGs,382
|
47
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/commandlog.html,sha256=3oXOw27_q8kC1d9PqPvzbnDUGQyHhCfKLyYtcV5AWDs,5672
|
48
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/commandlog_list.html,sha256=ggZDtjUOx4kyyD0zAybImF4_gGqe7eTx6JOx_CxjnQ0,105
|
49
|
+
netbox_toolkit_plugin/templates/netbox_toolkit_plugin/device_toolkit.html,sha256=WzoQwG9w1-vnpAfbPJ-m6-LyT8qJas9c2CfMkf2EanU,32356
|
50
|
+
netbox_toolkit_plugin/utils/__init__.py,sha256=YPk8W2lP8BYeYpZFzemTS4V4BK9s5DqXrAjyQrMAAeg,43
|
51
|
+
netbox_toolkit_plugin/utils/connection.py,sha256=XWUNOaJ9TBIO6XuESTPi3RSwe8bKlyZ0MLV4_b-ZJiY,4349
|
52
|
+
netbox_toolkit_plugin/utils/error_parser.py,sha256=tl6QrFW_0bugZKXHl5V_UPY3VDKdp6jDskLtM5oaYAI,16811
|
53
|
+
netbox_toolkit_plugin/utils/logging.py,sha256=AfIPUNUUKYyVqGqTiS7xi96KBNs5BYZjEK8_61SPnOk,1916
|
54
|
+
netbox_toolkit_plugin/utils/network.py,sha256=__Xz3pYhd7FtMYAsnFZGbW2akQ8uRY2tjs4axIoZMhA,6100
|
55
|
+
netbox_toolkit_plugin-0.1.2.dist-info/licenses/LICENSE,sha256=oxRw-SpalZWfCdc8ZALEDboD9OV22cBRrLIpXz2f7a8,11273
|
56
|
+
netbox_toolkit_plugin-0.1.2.dist-info/METADATA,sha256=IgLYRDTT9MX-4Xb1aDUSgPKB-baSFJi__Pa1v8El7vA,3157
|
57
|
+
netbox_toolkit_plugin-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
58
|
+
netbox_toolkit_plugin-0.1.2.dist-info/entry_points.txt,sha256=CwLAZ6veCye0uzfADprW9FSnOkWEPj2NGiO8usdW_oE,62
|
59
|
+
netbox_toolkit_plugin-0.1.2.dist-info/top_level.txt,sha256=XScXYui_2rj4aEFemGc-r5CqKgcmq_dwHgQy-SodK0Q,22
|
60
|
+
netbox_toolkit_plugin-0.1.2.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
netbox_toolkit_plugin
|
netbox_toolkit/__init__.py
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
from netbox.plugins import PluginConfig
|
2
|
-
|
3
|
-
class ToolkitConfig(PluginConfig):
|
4
|
-
name = 'netbox_toolkit_plugin'
|
5
|
-
verbose_name = 'Netbox Command Toolkit Plugin'
|
6
|
-
description = 'NetBox plugin for running pre-defined commands on network devices'
|
7
|
-
version = '0.1.0'
|
8
|
-
author = 'Andy Norwood'
|
9
|
-
base_url = 'toolkit'
|
10
|
-
|
11
|
-
# Database migrations
|
12
|
-
required_settings = []
|
13
|
-
|
14
|
-
# Default plugin settings
|
15
|
-
default_settings = {
|
16
|
-
'rate_limiting_enabled': True,
|
17
|
-
'device_command_limit': 10,
|
18
|
-
'time_window_minutes': 5,
|
19
|
-
'bypass_users': [],
|
20
|
-
'bypass_groups': [],
|
21
|
-
'debug_logging': False, # Enable debug logging for this plugin
|
22
|
-
}
|
23
|
-
|
24
|
-
# Middleware
|
25
|
-
middleware = []
|
26
|
-
|
27
|
-
# Django apps to load when plugin is activated
|
28
|
-
django_apps = []
|
29
|
-
|
30
|
-
config = ToolkitConfig
|
netbox_toolkit/config.py
DELETED
@@ -1,159 +0,0 @@
|
|
1
|
-
"""Configuration settings for the NetBox Toolkit plugin."""
|
2
|
-
from typing import Dict, Any
|
3
|
-
from django.conf import settings
|
4
|
-
|
5
|
-
|
6
|
-
class ToolkitConfig:
|
7
|
-
"""Configuration class for toolkit settings."""
|
8
|
-
|
9
|
-
# Default connection timeouts
|
10
|
-
DEFAULT_TIMEOUTS = {
|
11
|
-
'socket': 15,
|
12
|
-
'transport': 15,
|
13
|
-
'ops': 30,
|
14
|
-
'banner': 15,
|
15
|
-
'auth': 15,
|
16
|
-
}
|
17
|
-
|
18
|
-
# Device-specific timeout overrides
|
19
|
-
DEVICE_TIMEOUTS = {
|
20
|
-
'catalyst': {
|
21
|
-
'socket': 20,
|
22
|
-
'transport': 20,
|
23
|
-
'ops': 45,
|
24
|
-
},
|
25
|
-
'nexus': {
|
26
|
-
'socket': 25,
|
27
|
-
'transport': 25,
|
28
|
-
'ops': 60,
|
29
|
-
},
|
30
|
-
}
|
31
|
-
|
32
|
-
# SSH transport options
|
33
|
-
SSH_TRANSPORT_OPTIONS = {
|
34
|
-
'disabled_algorithms': {
|
35
|
-
'kex': [], # Don't disable any key exchange methods
|
36
|
-
},
|
37
|
-
'allowed_kex': [
|
38
|
-
# Modern algorithms
|
39
|
-
'diffie-hellman-group-exchange-sha256',
|
40
|
-
'diffie-hellman-group16-sha512',
|
41
|
-
'diffie-hellman-group18-sha512',
|
42
|
-
'diffie-hellman-group14-sha256',
|
43
|
-
# Legacy algorithms for older devices
|
44
|
-
'diffie-hellman-group-exchange-sha1',
|
45
|
-
'diffie-hellman-group14-sha1',
|
46
|
-
'diffie-hellman-group1-sha1',
|
47
|
-
],
|
48
|
-
}
|
49
|
-
|
50
|
-
# Netmiko configuration for fallback connections
|
51
|
-
NETMIKO_CONFIG = {
|
52
|
-
'banner_timeout': 20,
|
53
|
-
'auth_timeout': 20,
|
54
|
-
'global_delay_factor': 1,
|
55
|
-
'use_keys': False, # Disable SSH key authentication
|
56
|
-
'allow_agent': False, # Disable SSH agent
|
57
|
-
# Session logging (disabled by default)
|
58
|
-
'session_log': None,
|
59
|
-
# Connection options for legacy devices
|
60
|
-
'fast_cli': False, # Disable for older devices
|
61
|
-
'session_log_record_writes': False,
|
62
|
-
'session_log_file_mode': 'write',
|
63
|
-
}
|
64
|
-
|
65
|
-
# Retry configuration
|
66
|
-
RETRY_CONFIG = {
|
67
|
-
'max_retries': 2,
|
68
|
-
'retry_delay': 1, # Reduced from 3s to 1s for faster fallback
|
69
|
-
'backoff_multiplier': 1.5, # Reduced from 2 to 1.5 for faster progression
|
70
|
-
}
|
71
|
-
|
72
|
-
# Fast connection test timeouts (for initial Scrapli viability testing)
|
73
|
-
FAST_TEST_TIMEOUTS = {
|
74
|
-
'socket': 8, # Reduced from 15s to 8s for faster detection
|
75
|
-
'transport': 8, # Reduced from 15s to 8s for faster detection
|
76
|
-
'ops': 15, # Keep ops timeout reasonable for actual commands
|
77
|
-
}
|
78
|
-
|
79
|
-
# Error patterns that should trigger immediate fallback to Netmiko
|
80
|
-
SCRAPLI_FAST_FAIL_PATTERNS = [
|
81
|
-
"No matching key exchange",
|
82
|
-
"No matching cipher",
|
83
|
-
"No matching MAC",
|
84
|
-
"connection not opened",
|
85
|
-
"Error reading SSH protocol banner",
|
86
|
-
"Connection refused",
|
87
|
-
"Operation timed out",
|
88
|
-
"SSH handshake failed",
|
89
|
-
"Protocol version not supported",
|
90
|
-
"Unable to connect to port 22",
|
91
|
-
"Name or service not known",
|
92
|
-
"Network is unreachable"
|
93
|
-
]
|
94
|
-
|
95
|
-
# Platform mappings for better recognition
|
96
|
-
PLATFORM_ALIASES = {
|
97
|
-
'ios': 'cisco_ios',
|
98
|
-
'iosxe': 'cisco_ios',
|
99
|
-
'nxos': 'cisco_nxos',
|
100
|
-
'iosxr': 'cisco_iosxr',
|
101
|
-
'junos': 'juniper_junos',
|
102
|
-
'eos': 'arista_eos',
|
103
|
-
}
|
104
|
-
|
105
|
-
@classmethod
|
106
|
-
def get_fast_test_timeouts(cls) -> Dict[str, int]:
|
107
|
-
"""Get fast connection test timeouts for initial viability testing."""
|
108
|
-
return cls.FAST_TEST_TIMEOUTS.copy()
|
109
|
-
|
110
|
-
@classmethod
|
111
|
-
def should_fast_fail_to_netmiko(cls, error_message: str) -> bool:
|
112
|
-
"""Check if error message indicates immediate fallback to Netmiko is needed."""
|
113
|
-
error_lower = error_message.lower()
|
114
|
-
return any(pattern.lower() in error_lower for pattern in cls.SCRAPLI_FAST_FAIL_PATTERNS)
|
115
|
-
|
116
|
-
@classmethod
|
117
|
-
def get_timeouts_for_device(cls, device_type_model: str = None) -> Dict[str, int]:
|
118
|
-
"""Get timeout configuration for a specific device type."""
|
119
|
-
timeouts = cls.DEFAULT_TIMEOUTS.copy()
|
120
|
-
|
121
|
-
if device_type_model:
|
122
|
-
model_lower = device_type_model.lower()
|
123
|
-
for device_keyword, custom_timeouts in cls.DEVICE_TIMEOUTS.items():
|
124
|
-
if device_keyword in model_lower:
|
125
|
-
timeouts.update(custom_timeouts)
|
126
|
-
break
|
127
|
-
|
128
|
-
return timeouts
|
129
|
-
|
130
|
-
@classmethod
|
131
|
-
def normalize_platform(cls, platform: str) -> str:
|
132
|
-
"""Normalize platform name using aliases."""
|
133
|
-
if not platform:
|
134
|
-
return None
|
135
|
-
|
136
|
-
platform_lower = platform.lower()
|
137
|
-
return cls.PLATFORM_ALIASES.get(platform_lower, platform_lower)
|
138
|
-
|
139
|
-
@classmethod
|
140
|
-
def get_ssh_options(cls) -> Dict[str, Any]:
|
141
|
-
"""Get SSH transport options."""
|
142
|
-
return cls.SSH_TRANSPORT_OPTIONS.copy()
|
143
|
-
|
144
|
-
@classmethod
|
145
|
-
def get_retry_config(cls) -> Dict[str, int]:
|
146
|
-
"""Get retry configuration."""
|
147
|
-
return cls.RETRY_CONFIG.copy()
|
148
|
-
|
149
|
-
@classmethod
|
150
|
-
def get_ssh_transport_options(cls) -> Dict[str, Any]:
|
151
|
-
"""Get SSH transport options for Scrapli."""
|
152
|
-
user_config = getattr(settings, 'NETBOX_TOOLKIT', {})
|
153
|
-
return {**cls.SSH_TRANSPORT_OPTIONS, **user_config.get('ssh_options', {})}
|
154
|
-
|
155
|
-
@classmethod
|
156
|
-
def get_netmiko_config(cls) -> Dict[str, Any]:
|
157
|
-
"""Get Netmiko configuration for fallback connections."""
|
158
|
-
user_config = getattr(settings, 'NETBOX_TOOLKIT', {})
|
159
|
-
return {**cls.NETMIKO_CONFIG, **user_config.get('netmiko', {})}
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# Generated by Django 5.1.4 on 2025-05-23 14:41
|
2
|
-
|
3
|
-
import django.db.models.deletion
|
4
|
-
import taggit.managers
|
5
|
-
import utilities.json
|
6
|
-
from django.db import migrations, models
|
7
|
-
|
8
|
-
|
9
|
-
class Migration(migrations.Migration):
|
10
|
-
|
11
|
-
initial = True
|
12
|
-
|
13
|
-
dependencies = [
|
14
|
-
('dcim', '0200_populate_mac_addresses'),
|
15
|
-
('extras', '0122_charfield_null_choices'),
|
16
|
-
]
|
17
|
-
|
18
|
-
operations = [
|
19
|
-
migrations.CreateModel(
|
20
|
-
name='Command',
|
21
|
-
fields=[
|
22
|
-
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
23
|
-
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
24
|
-
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
25
|
-
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
26
|
-
('name', models.CharField(max_length=100)),
|
27
|
-
('command', models.TextField()),
|
28
|
-
('description', models.TextField(blank=True)),
|
29
|
-
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='toolkit_commands', to='dcim.devicetype')),
|
30
|
-
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
31
|
-
],
|
32
|
-
options={
|
33
|
-
'abstract': False,
|
34
|
-
},
|
35
|
-
),
|
36
|
-
migrations.CreateModel(
|
37
|
-
name='CommandLog',
|
38
|
-
fields=[
|
39
|
-
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
40
|
-
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
41
|
-
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
42
|
-
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
43
|
-
('output', models.TextField()),
|
44
|
-
('username', models.CharField(max_length=100)),
|
45
|
-
('execution_time', models.DateTimeField(auto_now_add=True)),
|
46
|
-
('command', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='netbox_toolkit.command')),
|
47
|
-
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='command_logs', to='dcim.device')),
|
48
|
-
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
49
|
-
],
|
50
|
-
options={
|
51
|
-
'abstract': False,
|
52
|
-
},
|
53
|
-
),
|
54
|
-
]
|
netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
# Generated by Django 5.1.4 on 2025-05-26 12:11
|
2
|
-
|
3
|
-
import django.db.models.deletion
|
4
|
-
from django.db import migrations, models
|
5
|
-
|
6
|
-
|
7
|
-
class Migration(migrations.Migration):
|
8
|
-
|
9
|
-
dependencies = [
|
10
|
-
('dcim', '0200_populate_mac_addresses'),
|
11
|
-
('netbox_toolkit', '0001_initial'),
|
12
|
-
]
|
13
|
-
|
14
|
-
operations = [
|
15
|
-
# First remove the old field
|
16
|
-
migrations.RemoveField(
|
17
|
-
model_name='command',
|
18
|
-
name='device_type',
|
19
|
-
),
|
20
|
-
# Add all new fields
|
21
|
-
migrations.AddField(
|
22
|
-
model_name='command',
|
23
|
-
name='platform',
|
24
|
-
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='toolkit_commands', to='dcim.platform'),
|
25
|
-
preserve_default=False,
|
26
|
-
),
|
27
|
-
migrations.AddField(
|
28
|
-
model_name='command',
|
29
|
-
name='command_type',
|
30
|
-
field=models.CharField(default='show', max_length=50),
|
31
|
-
),
|
32
|
-
migrations.AddField(
|
33
|
-
model_name='command',
|
34
|
-
name='requires_config_mode',
|
35
|
-
field=models.BooleanField(default=False),
|
36
|
-
),
|
37
|
-
migrations.AddField(
|
38
|
-
model_name='command',
|
39
|
-
name='requires_enable',
|
40
|
-
field=models.BooleanField(default=False),
|
41
|
-
),
|
42
|
-
migrations.AddField(
|
43
|
-
model_name='commandlog',
|
44
|
-
name='error_message',
|
45
|
-
field=models.TextField(blank=True),
|
46
|
-
),
|
47
|
-
migrations.AddField(
|
48
|
-
model_name='commandlog',
|
49
|
-
name='execution_duration',
|
50
|
-
field=models.FloatField(blank=True, null=True),
|
51
|
-
),
|
52
|
-
migrations.AddField(
|
53
|
-
model_name='commandlog',
|
54
|
-
name='success',
|
55
|
-
field=models.BooleanField(default=True),
|
56
|
-
),
|
57
|
-
# Then alter model options after fields exist
|
58
|
-
migrations.AlterModelOptions(
|
59
|
-
name='command',
|
60
|
-
options={'ordering': ['platform', 'name']},
|
61
|
-
),
|
62
|
-
migrations.AlterUniqueTogether(
|
63
|
-
name='command',
|
64
|
-
unique_together={('platform', 'name')},
|
65
|
-
),
|
66
|
-
]
|
netbox_toolkit/tables.py
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
import django_tables2 as tables
|
2
|
-
from netbox.tables import NetBoxTable, columns
|
3
|
-
from .models import Command, CommandLog
|
4
|
-
|
5
|
-
class CommandTable(NetBoxTable):
|
6
|
-
name = tables.Column(
|
7
|
-
linkify=('plugins:netbox_toolkit:command_detail', [tables.A('pk')])
|
8
|
-
)
|
9
|
-
platform = tables.Column(
|
10
|
-
linkify=True
|
11
|
-
)
|
12
|
-
command_type = tables.Column()
|
13
|
-
|
14
|
-
class Meta(NetBoxTable.Meta):
|
15
|
-
model = Command
|
16
|
-
fields = ('pk', 'id', 'name', 'platform', 'command_type', 'description')
|
17
|
-
default_columns = ('pk', 'name', 'platform', 'command_type', 'description')
|
18
|
-
|
19
|
-
class CommandLogTable(NetBoxTable):
|
20
|
-
command = tables.Column(
|
21
|
-
linkify=('plugins:netbox_toolkit:command_detail', [tables.A('command.pk')])
|
22
|
-
)
|
23
|
-
device = tables.Column(
|
24
|
-
linkify=True
|
25
|
-
)
|
26
|
-
success = tables.BooleanColumn(
|
27
|
-
verbose_name='Status',
|
28
|
-
yesno=('Success', 'Failed')
|
29
|
-
)
|
30
|
-
|
31
|
-
# Remove actions column entirely
|
32
|
-
actions = False
|
33
|
-
|
34
|
-
class Meta(NetBoxTable.Meta):
|
35
|
-
model = CommandLog
|
36
|
-
fields = ('pk', 'id', 'command', 'device', 'username', 'execution_time', 'success', 'execution_duration')
|
37
|
-
default_columns = ('pk', 'command', 'device', 'username', 'execution_time', 'success')
|
netbox_toolkit/urls.py
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
from django.urls import path
|
2
|
-
from . import views
|
3
|
-
|
4
|
-
app_name = 'netbox_toolkit'
|
5
|
-
|
6
|
-
urlpatterns = [
|
7
|
-
# Command views
|
8
|
-
path('commands/', views.CommandListView.as_view(), name='command_list'),
|
9
|
-
path('commands/add/', views.CommandEditView.as_view(), name='command_add'),
|
10
|
-
path('commands/<int:pk>/', views.CommandView.as_view(), name='command_detail'),
|
11
|
-
path('commands/<int:pk>/edit/', views.CommandEditView.as_view(), name='command_edit'),
|
12
|
-
path('commands/<int:pk>/delete/', views.CommandDeleteView.as_view(), name='command_delete'),
|
13
|
-
path('commands/<int:pk>/changelog/', views.CommandChangeLogView.as_view(), name='command_changelog'),
|
14
|
-
|
15
|
-
# Command Log views
|
16
|
-
path('logs/', views.CommandLogListView.as_view(), name='commandlog_list'),
|
17
|
-
path('logs/<int:pk>/', views.CommandLogView.as_view(), name='commandlog_view'),
|
18
|
-
path('logs/<int:pk>/changelog/', views.CommandLogChangeLogView.as_view(), name='commandlog_changelog'),
|
19
|
-
|
20
|
-
# Device toolkit view
|
21
|
-
path('devices/<int:pk>/toolkit/', views.DeviceToolkitView.as_view(), name='device_toolkit'),
|
22
|
-
]
|
@@ -1,56 +0,0 @@
|
|
1
|
-
netbox_toolkit/__init__.py,sha256=eRGsuq2UwhZmpxPq-G6JJoW66E4UpATmI1bhY1ztoVw,823
|
2
|
-
netbox_toolkit/admin.py,sha256=j0O2hke80XbGCfU3lkxl8__tRmBArNLoQtLyA39hepI,690
|
3
|
-
netbox_toolkit/config.py,sha256=IKDDR1CMzTNCL3DQPXVKZ7WCw6kl53PD9D4jrBs6fpc,5481
|
4
|
-
netbox_toolkit/exceptions.py,sha256=qh7SxXLTqwsduxUMiTA_pXQ67VHSpxRuQgdlfXMl6Bk,785
|
5
|
-
netbox_toolkit/filtersets.py,sha256=ppmnfQ8IUHm60BiJR9_cdn1ygvuFlMfj6YIFLJ-XeMc,2722
|
6
|
-
netbox_toolkit/forms.py,sha256=6aUaXuJtx3ZL2iEbIq2zXuiXCu2bAHDUMI2EJJBPsWg,1037
|
7
|
-
netbox_toolkit/models.py,sha256=G08tjm6N2Ob8yf3503VvzOy249iWDYlDIpyYwht75JM,2791
|
8
|
-
netbox_toolkit/navigation.py,sha256=Ew0rx2vcLqArvEcSb9WLImz0PUBWXNOMCMbPCUMV_Bk,941
|
9
|
-
netbox_toolkit/search.py,sha256=v8HPgp_0Nq-s_IoV7n4Kfi63_u535jVAoT-31ntdZlA,564
|
10
|
-
netbox_toolkit/tables.py,sha256=qiP2Mub4saH_EhaZd43zf7wOfjgBBlMXA5Rj6iorDQ0,1238
|
11
|
-
netbox_toolkit/urls.py,sha256=Kr9xkrK_CXHGjCOTlNRE-8siqFH6nhqi74Um14LHcvA,1079
|
12
|
-
netbox_toolkit/views.py,sha256=K-LNcJoSYbAXDstxqA_hRemhR5wIf9-xk8bvqbSt428,17730
|
13
|
-
netbox_toolkit/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
netbox_toolkit/api/mixins.py,sha256=Yxog3LQQqmzIiXjxZ5VxSbwV3er-Pxs2GpPEJUipvfY,2241
|
15
|
-
netbox_toolkit/api/schemas.py,sha256=Xza5-H2ZqBPCEPyMzDkbmSofY9nE73epu0PKvCB1_Ys,7334
|
16
|
-
netbox_toolkit/api/serializers.py,sha256=ZSfBmICUwLEQoMvxPK5348mG7j5v3I5A4Yvu2fLui_Y,5791
|
17
|
-
netbox_toolkit/api/urls.py,sha256=sllNjhMFm6pjTXtVphAJg2FU9Am_v0AQy0xtRwrZbQA,273
|
18
|
-
netbox_toolkit/api/views/__init__.py,sha256=CksHVk9Jj81kImVlr_JnP9VGwFCAhtrnRzqw7H7pjcI,189
|
19
|
-
netbox_toolkit/api/views/command_logs.py,sha256=4PdUQPvle9R9pJcJalYS4MCKYhmG_5EbAUowbjVetgw,6255
|
20
|
-
netbox_toolkit/api/views/commands.py,sha256=dv6sjN3L0FTyAWmM4u7zZBPOug6H_WcIR6egd3XE3A4,10939
|
21
|
-
netbox_toolkit/connectors/__init__.py,sha256=lL1WCo-rlcjNGVwmwSN1PT592xr5LEPT8k5zEVZlltE,419
|
22
|
-
netbox_toolkit/connectors/base.py,sha256=Z88tVG87H8IXdub55t-_uu1ptdhKo0uaz9l9iO-Xi0I,2829
|
23
|
-
netbox_toolkit/connectors/factory.py,sha256=XeyIooBu1CwZpvtE9giFaFc5kKODck3fMQwY0OVWALU,13296
|
24
|
-
netbox_toolkit/connectors/netmiko_connector.py,sha256=4DZFpvatSQgjG6wp0reYYCwpwjGzVDILvh98QkYsM8o,18793
|
25
|
-
netbox_toolkit/connectors/scrapli_connector.py,sha256=ojQgXkUZVQ9vFAdtKEM4aA5HUX7JDO8mLPe4r_YTd7c,22859
|
26
|
-
netbox_toolkit/migrations/0001_initial.py,sha256=O8DWY0jMBfRLgxaCETgjOVxbcE0Ngr7nVXVg_zJCJwA,2533
|
27
|
-
netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py,sha256=8DaaDfzzkVQB4ZEGhoWjyjncvUPjCwNDrKaToFtfmsY,2089
|
28
|
-
netbox_toolkit/migrations/0003_permission_system_update.py,sha256=KDhXY9UGiBpXLfL_4yPEQfzm5F_bGXmjy10upuykQ_c,1783
|
29
|
-
netbox_toolkit/migrations/0004_remove_django_permissions.py,sha256=o_-5dWkSL1JtASkLpGPFg3bDi_Vx63USMX9sw6zNv_4,2638
|
30
|
-
netbox_toolkit/migrations/0005_alter_command_options_and_more.py,sha256=O7VQ0Nq4xpyheX8BmsoOL-Y7fyEvXyPTx503WSce0Is,603
|
31
|
-
netbox_toolkit/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py,sha256=1NyrQuBdnFEU7oTE_SIOHPLeFzNuOAP51Q5n8y_Sifc,765
|
32
|
-
netbox_toolkit/migrations/0007_alter_commandlog_parsing_template.py,sha256=SqVl4LafwmGm2sTKhsAvvrJkjm4cD5Oa3EwTaqZXaeU,471
|
33
|
-
netbox_toolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
|
-
netbox_toolkit/services/__init__.py,sha256=nzdv0yOAyoAgm3ReKn-zLCbxkzEZfDpfnVDy9UExtyg,273
|
35
|
-
netbox_toolkit/services/command_service.py,sha256=3dDrTNmdh1I25ZfzjXFa5-rP4K7sWo9HuimP28LrGMg,15159
|
36
|
-
netbox_toolkit/services/device_service.py,sha256=SWqWw7c5cBhbXE_jCJwfgc3KvxchQq_6NeS2hlb3NDA,2945
|
37
|
-
netbox_toolkit/services/rate_limiting_service.py,sha256=63plMX25qJkiEztRt8NnJMDRQlp7QVtI6TNSfk8IPTc,8750
|
38
|
-
netbox_toolkit/static/netbox_toolkit/css/toolkit.css,sha256=VmyM2_rBJhiP43whtMFaYYP4EW4MUGHPPr84mOo_0Qg,4236
|
39
|
-
netbox_toolkit/static/netbox_toolkit/js/toolkit.js,sha256=0oF2p8g1Yu28hKQswj1-2Kctt_09YtT__Xeo39LfQ-I,25721
|
40
|
-
netbox_toolkit/templates/netbox_toolkit/command.html,sha256=lTLe4TJmXtiOx1lSbowegwklLY1te7vfieXM3gVtZYs,3387
|
41
|
-
netbox_toolkit/templates/netbox_toolkit/command_edit.html,sha256=Dx3QVrtLegViZl9_VWeVayZ1DIgtzWDnrmgS5_TfOCg,175
|
42
|
-
netbox_toolkit/templates/netbox_toolkit/command_list.html,sha256=SVgov1SbpBj_hgmnmgpajAT5_ur-lxzK8PinXp5eEIY,368
|
43
|
-
netbox_toolkit/templates/netbox_toolkit/commandlog.html,sha256=8qix7GG937QPRxquUEj-GUGDQOxdjX0ERKveQ6OyuMo,5658
|
44
|
-
netbox_toolkit/templates/netbox_toolkit/commandlog_list.html,sha256=ggZDtjUOx4kyyD0zAybImF4_gGqe7eTx6JOx_CxjnQ0,105
|
45
|
-
netbox_toolkit/templates/netbox_toolkit/device_toolkit.html,sha256=f-hGEXqQhf3FWHQNCUNw1vlA6jJfDWm3AdPgggtuoW0,32314
|
46
|
-
netbox_toolkit/utils/__init__.py,sha256=YPk8W2lP8BYeYpZFzemTS4V4BK9s5DqXrAjyQrMAAeg,43
|
47
|
-
netbox_toolkit/utils/connection.py,sha256=XWUNOaJ9TBIO6XuESTPi3RSwe8bKlyZ0MLV4_b-ZJiY,4349
|
48
|
-
netbox_toolkit/utils/error_parser.py,sha256=tl6QrFW_0bugZKXHl5V_UPY3VDKdp6jDskLtM5oaYAI,16811
|
49
|
-
netbox_toolkit/utils/logging.py,sha256=zxLy4c5FXaSR-ndVvY8OnKEn6hyvEDJRMKSDKc8ukjo,1933
|
50
|
-
netbox_toolkit/utils/network.py,sha256=__Xz3pYhd7FtMYAsnFZGbW2akQ8uRY2tjs4axIoZMhA,6100
|
51
|
-
netbox_toolkit_plugin-0.1.0.dist-info/licenses/LICENSE,sha256=oxRw-SpalZWfCdc8ZALEDboD9OV22cBRrLIpXz2f7a8,11273
|
52
|
-
netbox_toolkit_plugin-0.1.0.dist-info/METADATA,sha256=OIKy1epeypZpwW_GTHKySD2GnA0BqgJidd4yNehJ1MI,3157
|
53
|
-
netbox_toolkit_plugin-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
54
|
-
netbox_toolkit_plugin-0.1.0.dist-info/entry_points.txt,sha256=QG7lS9AhTN2AdEwFbO-DIyXVW4Q1avIC0Ha-CdNT4VA,69
|
55
|
-
netbox_toolkit_plugin-0.1.0.dist-info/top_level.txt,sha256=uPBIiwEx-X6sCRLNFfFtd7LN7zq2zJWIkP-hReJrKfY,15
|
56
|
-
netbox_toolkit_plugin-0.1.0.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
netbox_toolkit
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/licenses/LICENSE
RENAMED
File without changes
|