netbox-toolkit-plugin 0.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.
Files changed (56) hide show
  1. netbox_toolkit/__init__.py +30 -0
  2. netbox_toolkit/admin.py +16 -0
  3. netbox_toolkit/api/__init__.py +0 -0
  4. netbox_toolkit/api/mixins.py +54 -0
  5. netbox_toolkit/api/schemas.py +234 -0
  6. netbox_toolkit/api/serializers.py +158 -0
  7. netbox_toolkit/api/urls.py +10 -0
  8. netbox_toolkit/api/views/__init__.py +10 -0
  9. netbox_toolkit/api/views/command_logs.py +170 -0
  10. netbox_toolkit/api/views/commands.py +267 -0
  11. netbox_toolkit/config.py +159 -0
  12. netbox_toolkit/connectors/__init__.py +15 -0
  13. netbox_toolkit/connectors/base.py +97 -0
  14. netbox_toolkit/connectors/factory.py +301 -0
  15. netbox_toolkit/connectors/netmiko_connector.py +443 -0
  16. netbox_toolkit/connectors/scrapli_connector.py +486 -0
  17. netbox_toolkit/exceptions.py +36 -0
  18. netbox_toolkit/filtersets.py +85 -0
  19. netbox_toolkit/forms.py +31 -0
  20. netbox_toolkit/migrations/0001_initial.py +54 -0
  21. netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +66 -0
  22. netbox_toolkit/migrations/0003_permission_system_update.py +48 -0
  23. netbox_toolkit/migrations/0004_remove_django_permissions.py +77 -0
  24. netbox_toolkit/migrations/0005_alter_command_options_and_more.py +25 -0
  25. netbox_toolkit/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py +28 -0
  26. netbox_toolkit/migrations/0007_alter_commandlog_parsing_template.py +18 -0
  27. netbox_toolkit/migrations/__init__.py +0 -0
  28. netbox_toolkit/models.py +89 -0
  29. netbox_toolkit/navigation.py +30 -0
  30. netbox_toolkit/search.py +21 -0
  31. netbox_toolkit/services/__init__.py +7 -0
  32. netbox_toolkit/services/command_service.py +357 -0
  33. netbox_toolkit/services/device_service.py +87 -0
  34. netbox_toolkit/services/rate_limiting_service.py +228 -0
  35. netbox_toolkit/static/netbox_toolkit/css/toolkit.css +143 -0
  36. netbox_toolkit/static/netbox_toolkit/js/toolkit.js +657 -0
  37. netbox_toolkit/tables.py +37 -0
  38. netbox_toolkit/templates/netbox_toolkit/command.html +108 -0
  39. netbox_toolkit/templates/netbox_toolkit/command_edit.html +10 -0
  40. netbox_toolkit/templates/netbox_toolkit/command_list.html +12 -0
  41. netbox_toolkit/templates/netbox_toolkit/commandlog.html +170 -0
  42. netbox_toolkit/templates/netbox_toolkit/commandlog_list.html +4 -0
  43. netbox_toolkit/templates/netbox_toolkit/device_toolkit.html +536 -0
  44. netbox_toolkit/urls.py +22 -0
  45. netbox_toolkit/utils/__init__.py +1 -0
  46. netbox_toolkit/utils/connection.py +125 -0
  47. netbox_toolkit/utils/error_parser.py +428 -0
  48. netbox_toolkit/utils/logging.py +58 -0
  49. netbox_toolkit/utils/network.py +157 -0
  50. netbox_toolkit/views.py +385 -0
  51. netbox_toolkit_plugin-0.1.0.dist-info/METADATA +76 -0
  52. netbox_toolkit_plugin-0.1.0.dist-info/RECORD +56 -0
  53. netbox_toolkit_plugin-0.1.0.dist-info/WHEEL +5 -0
  54. netbox_toolkit_plugin-0.1.0.dist-info/entry_points.txt +2 -0
  55. netbox_toolkit_plugin-0.1.0.dist-info/licenses/LICENSE +200 -0
  56. netbox_toolkit_plugin-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,54 @@
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
+ ]
@@ -0,0 +1,66 @@
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
+ ]
@@ -0,0 +1,48 @@
1
+ # Generated by Django 5.1.4 on 2025-05-26 15:28
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def migrate_command_types(apps, schema_editor):
7
+ """
8
+ Migrate existing diagnostic and troubleshooting commands to 'show' type
9
+ """
10
+ Command = apps.get_model('netbox_toolkit', 'Command')
11
+
12
+ # Update diagnostic commands to show
13
+ diagnostic_count = Command.objects.filter(command_type='diagnostic').update(command_type='show')
14
+
15
+ # Update troubleshooting commands to show
16
+ troubleshooting_count = Command.objects.filter(command_type='troubleshooting').update(command_type='show')
17
+
18
+ print(f"Migration: Updated {diagnostic_count} diagnostic commands to 'show' type")
19
+ print(f"Migration: Updated {troubleshooting_count} troubleshooting commands to 'show' type")
20
+
21
+
22
+ def reverse_migrate_command_types(apps, schema_editor):
23
+ """
24
+ Reverse migration - this is a simplified reverse that sets all to 'show'
25
+ since we can't reliably determine original diagnostic vs troubleshooting
26
+ """
27
+ # Note: This is a lossy reverse operation
28
+ # In practice, this is acceptable since diagnostic/troubleshooting
29
+ # were being removed as part of the permission system design
30
+ pass
31
+
32
+
33
+ class Migration(migrations.Migration):
34
+
35
+ dependencies = [
36
+ ('netbox_toolkit', '0002_alter_command_options_alter_command_unique_together_and_more'),
37
+ ]
38
+
39
+ operations = [
40
+ migrations.AlterModelOptions(
41
+ name='command',
42
+ options={'ordering': ['platform', 'name'], 'permissions': [('execute_show_command', 'Can execute show commands'), ('execute_config_command', 'Can execute configuration commands')]},
43
+ ),
44
+ migrations.RunPython(
45
+ migrate_command_types,
46
+ reverse_migrate_command_types,
47
+ ),
48
+ ]
@@ -0,0 +1,77 @@
1
+ # Generated migration to remove Django custom permissions for object-based permissions migration
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def remove_django_permissions(apps, schema_editor):
7
+ """Remove old Django permissions that are no longer needed"""
8
+ Permission = apps.get_model('auth', 'Permission')
9
+ ContentType = apps.get_model('contenttypes', 'ContentType')
10
+
11
+ try:
12
+ # Get the Command content type
13
+ command_ct = ContentType.objects.get(app_label='netbox_toolkit', model='command')
14
+
15
+ # Remove the custom Django permissions
16
+ custom_permissions = [
17
+ 'execute_show_command',
18
+ 'execute_config_command'
19
+ ]
20
+
21
+ deleted_count = 0
22
+ for codename in custom_permissions:
23
+ count, _ = Permission.objects.filter(
24
+ content_type=command_ct,
25
+ codename=codename
26
+ ).delete()
27
+ deleted_count += count
28
+
29
+ print(f"Removed {deleted_count} old Django permissions")
30
+
31
+ except ContentType.DoesNotExist:
32
+ # Command model doesn't exist yet, skip
33
+ print("Command model not found, skipping permission removal")
34
+ except Exception as e:
35
+ print(f"Warning: Could not remove old permissions: {e}")
36
+
37
+ def restore_django_permissions(apps, schema_editor):
38
+ """Restore Django permissions if migration is reversed"""
39
+ Permission = apps.get_model('auth', 'Permission')
40
+ ContentType = apps.get_model('contenttypes', 'ContentType')
41
+
42
+ try:
43
+ # Get the Command content type
44
+ command_ct = ContentType.objects.get(app_label='netbox_toolkit', model='command')
45
+
46
+ # Recreate the custom Django permissions
47
+ permissions_to_create = [
48
+ ('execute_show_command', 'Can execute show commands'),
49
+ ('execute_config_command', 'Can execute configuration commands'),
50
+ ]
51
+
52
+ for codename, name in permissions_to_create:
53
+ Permission.objects.get_or_create(
54
+ content_type=command_ct,
55
+ codename=codename,
56
+ defaults={'name': name}
57
+ )
58
+
59
+ print("Restored old Django permissions")
60
+
61
+ except ContentType.DoesNotExist:
62
+ # Command model doesn't exist, skip
63
+ print("Command model not found, skipping permission restoration")
64
+
65
+
66
+ class Migration(migrations.Migration):
67
+
68
+ dependencies = [
69
+ ('netbox_toolkit', '0003_permission_system_update'),
70
+ ]
71
+
72
+ operations = [
73
+ migrations.RunPython(
74
+ remove_django_permissions,
75
+ restore_django_permissions,
76
+ ),
77
+ ]
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.1.4 on 2025-05-27 12:54
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('netbox_toolkit', '0004_remove_django_permissions'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='command',
15
+ options={'ordering': ['platform', 'name']},
16
+ ),
17
+ migrations.RemoveField(
18
+ model_name='command',
19
+ name='requires_config_mode',
20
+ ),
21
+ migrations.RemoveField(
22
+ model_name='command',
23
+ name='requires_enable',
24
+ ),
25
+ ]
@@ -0,0 +1,28 @@
1
+ # Generated by Django 5.1.4 on 2025-05-29 11:47
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('netbox_toolkit', '0005_alter_command_options_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='commandlog',
15
+ name='parsed_data',
16
+ field=models.JSONField(blank=True, null=True),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='commandlog',
20
+ name='parsing_success',
21
+ field=models.BooleanField(default=False),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='commandlog',
25
+ name='parsing_template',
26
+ field=models.CharField(blank=True, max_length=255),
27
+ ),
28
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.1.4 on 2025-05-29 12:00
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('netbox_toolkit', '0006_commandlog_parsed_data_commandlog_parsing_success_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='commandlog',
15
+ name='parsing_template',
16
+ field=models.CharField(blank=True, max_length=255, null=True),
17
+ ),
18
+ ]
File without changes
@@ -0,0 +1,89 @@
1
+ from django.db import models
2
+ from django.core.exceptions import ValidationError
3
+ from netbox.models import NetBoxModel
4
+ from dcim.models import Device
5
+
6
+ class Command(NetBoxModel):
7
+ name = models.CharField(max_length=100)
8
+ command = models.TextField()
9
+ description = models.TextField(blank=True)
10
+
11
+ # Platform-based association (required)
12
+ platform = models.ForeignKey(
13
+ to='dcim.Platform',
14
+ on_delete=models.CASCADE,
15
+ related_name='toolkit_commands',
16
+ help_text="Platform this command is designed for (e.g., cisco_ios, cisco_nxos, generic)"
17
+ )
18
+
19
+ # Command categorization
20
+ command_type = models.CharField(
21
+ max_length=50,
22
+ choices=[
23
+ ('show', 'Show Command'),
24
+ ('config', 'Configuration Command'),
25
+ ],
26
+ default='show',
27
+ help_text="Type of command for categorization and permission control"
28
+ )
29
+
30
+ class Meta:
31
+ ordering = ['platform', 'name']
32
+ unique_together = [['platform', 'name']]
33
+
34
+ def __str__(self):
35
+ return f"{self.name} ({self.platform})"
36
+
37
+ def get_absolute_url(self):
38
+ """Return the URL for this object"""
39
+ from django.urls import reverse
40
+ return reverse('plugins:netbox_toolkit:command_detail', kwargs={'pk': self.pk})
41
+
42
+ class CommandLog(NetBoxModel):
43
+ command = models.ForeignKey(
44
+ to=Command,
45
+ on_delete=models.CASCADE,
46
+ related_name='logs'
47
+ )
48
+ device = models.ForeignKey(
49
+ to='dcim.Device',
50
+ on_delete=models.CASCADE,
51
+ related_name='command_logs'
52
+ )
53
+ output = models.TextField()
54
+ username = models.CharField(max_length=100)
55
+ execution_time = models.DateTimeField(auto_now_add=True)
56
+
57
+ # Execution details added in migration
58
+ success = models.BooleanField(default=True)
59
+ error_message = models.TextField(blank=True)
60
+ execution_duration = models.FloatField(
61
+ blank=True,
62
+ null=True,
63
+ help_text="Command execution time in seconds"
64
+ )
65
+
66
+ # Parsing details for TextFSM
67
+ parsed_data = models.JSONField(
68
+ blank=True,
69
+ null=True,
70
+ help_text="Parsed structured data from command output"
71
+ )
72
+ parsing_success = models.BooleanField(
73
+ default=False,
74
+ help_text="Whether the output was successfully parsed"
75
+ )
76
+ parsing_template = models.CharField(
77
+ max_length=255,
78
+ blank=True,
79
+ null=True,
80
+ help_text="Name of the TextFSM template used for parsing"
81
+ )
82
+
83
+ def __str__(self):
84
+ return f"{self.command} on {self.device}"
85
+
86
+ def get_absolute_url(self):
87
+ """Return the URL for this object"""
88
+ from django.urls import reverse
89
+ return reverse('plugins:netbox_toolkit:commandlog_view', kwargs={'pk': self.pk})
@@ -0,0 +1,30 @@
1
+ from netbox.choices import ButtonColorChoices
2
+ from netbox.plugins import PluginMenu, PluginMenuButton, PluginMenuItem
3
+
4
+ menu = PluginMenu(
5
+ label="Command Toolkit",
6
+ icon_class="mdi mdi-console",
7
+ groups=(
8
+ (
9
+ "Commands",
10
+ (
11
+ PluginMenuItem(
12
+ link="plugins:netbox_toolkit:command_list",
13
+ link_text="Commands",
14
+ buttons=(
15
+ PluginMenuButton(
16
+ "plugins:netbox_toolkit:command_add",
17
+ "Add",
18
+ "mdi mdi-plus-thick",
19
+ ButtonColorChoices.GRAY
20
+ ),
21
+ )
22
+ ),
23
+ PluginMenuItem(
24
+ link="plugins:netbox_toolkit:commandlog_list",
25
+ link_text="Command Logs",
26
+ ),
27
+ ),
28
+ ),
29
+ ),
30
+ )
@@ -0,0 +1,21 @@
1
+ from netbox.search import SearchIndex
2
+ from .models import Command, CommandLog
3
+
4
+ class CommandIndex(SearchIndex):
5
+ model = Command
6
+ fields = (
7
+ ('name', 100),
8
+ ('command', 200),
9
+ ('description', 500),
10
+ )
11
+ display_attrs = ('platform', 'command_type', 'description')
12
+
13
+ class CommandLogIndex(SearchIndex):
14
+ model = CommandLog
15
+ fields = (
16
+ ('command__name', 100),
17
+ ('device__name', 150),
18
+ ('username', 200),
19
+ ('output', 1000),
20
+ )
21
+ display_attrs = ('command', 'device', 'success', 'execution_time')
@@ -0,0 +1,7 @@
1
+ """Services package for business logic."""
2
+
3
+ from .command_service import CommandExecutionService
4
+ from .device_service import DeviceService
5
+ from .rate_limiting_service import RateLimitingService
6
+
7
+ __all__ = ['CommandExecutionService', 'DeviceService', 'RateLimitingService']