ipfabric_netbox 4.1.0b3__tar.gz → 4.1.0b5__tar.gz
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.
Potentially problematic release.
This version of ipfabric_netbox might be problematic. Click here for more details.
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/PKG-INFO +1 -1
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/__init__.py +1 -1
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/nested_serializers.py +0 -1
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/serializers.py +23 -1
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/urls.py +2 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/views.py +7 -0
- ipfabric_netbox-4.1.0b5/ipfabric_netbox/exceptions.py +81 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/filtersets.py +30 -0
- ipfabric_netbox-4.1.0b5/ipfabric_netbox/migrations/0015_ipfabricingestionissue.py +48 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/models.py +25 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/tables.py +23 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html +18 -47
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +6 -1
- ipfabric_netbox-4.1.0b5/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html +37 -0
- ipfabric_netbox-4.1.0b5/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +1 -0
- ipfabric_netbox-4.1.0b5/ipfabric_netbox/templates/ipfabric_netbox/partials/object_tabs.html +12 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/utilities/ipfutils.py +158 -75
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/views.py +55 -30
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/pyproject.toml +1 -1
- ipfabric_netbox-4.1.0b3/ipfabric_netbox/exceptions.py +0 -24
- ipfabric_netbox-4.1.0b3/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +0 -1
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/README.md +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/__init__.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/choices.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/data/transform_map.json +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/forms.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/jobs.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0001_initial.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0007_prepare_custom_fields.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0008_prepare_transform_maps.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0012_remove_status_field.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/0014_ipfabrictransformmapgroup_ipfabrictransformmap_group.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/migrations/__init__.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/navigation.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/signals.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/template_content.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmapgroup.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_progress.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templatetags/__init__.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/tests/__init__.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/tests/test_models.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/urls.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/utilities/__init__.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/utilities/logging.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/utilities/nbutils.py +0 -0
- {ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/utilities/transform_map.py +0 -0
{ipfabric_netbox-4.1.0b3 → ipfabric_netbox-4.1.0b5}/ipfabric_netbox/api/nested_serializers.py
RENAMED
|
@@ -10,7 +10,6 @@ from ipfabric_netbox.models import IPFabricSource
|
|
|
10
10
|
from ipfabric_netbox.models import IPFabricSync
|
|
11
11
|
from ipfabric_netbox.models import IPFabricTransformMap
|
|
12
12
|
|
|
13
|
-
# from .serializers import IPFabricSyncSerializer
|
|
14
13
|
|
|
15
14
|
__all__ = (
|
|
16
15
|
"NestedIPFabricSourceSerializer",
|
|
@@ -7,8 +7,10 @@ from rest_framework import serializers
|
|
|
7
7
|
|
|
8
8
|
from .nested_serializers import NestedIPFabricSnapshotSerializer
|
|
9
9
|
from .nested_serializers import NestedIPFabricSourceSerializer
|
|
10
|
+
from .nested_serializers import NestedIPFabricSyncSerializer
|
|
10
11
|
from .nested_serializers import NestedIPFabricTransformMapSerializer
|
|
11
12
|
from ipfabric_netbox.models import IPFabricIngestion
|
|
13
|
+
from ipfabric_netbox.models import IPFabricIngestionIssue
|
|
12
14
|
from ipfabric_netbox.models import IPFabricRelationshipField
|
|
13
15
|
from ipfabric_netbox.models import IPFabricSnapshot
|
|
14
16
|
from ipfabric_netbox.models import IPFabricSource
|
|
@@ -23,7 +25,9 @@ __all__ = (
|
|
|
23
25
|
"IPFabricRelationshipFieldSerializer",
|
|
24
26
|
"IPFabricTransformFieldSerializer",
|
|
25
27
|
"IPFabricTransformMapSerializer",
|
|
28
|
+
"IPFabricTransformMapGroupSerializer",
|
|
26
29
|
"IPFabricIngestionSerializer",
|
|
30
|
+
"IPFabricIngestionIssueSerializer",
|
|
27
31
|
"IPFabricSourceSerializer",
|
|
28
32
|
)
|
|
29
33
|
|
|
@@ -133,7 +137,7 @@ class IPFabricTransformFieldSerializer(NetBoxModelSerializer):
|
|
|
133
137
|
|
|
134
138
|
class IPFabricIngestionSerializer(NetBoxModelSerializer):
|
|
135
139
|
branch = BranchSerializer(read_only=True)
|
|
136
|
-
sync =
|
|
140
|
+
sync = NestedIPFabricSyncSerializer(read_only=True)
|
|
137
141
|
|
|
138
142
|
class Meta:
|
|
139
143
|
model = IPFabricIngestion
|
|
@@ -145,6 +149,24 @@ class IPFabricIngestionSerializer(NetBoxModelSerializer):
|
|
|
145
149
|
]
|
|
146
150
|
|
|
147
151
|
|
|
152
|
+
class IPFabricIngestionIssueSerializer(NetBoxModelSerializer):
|
|
153
|
+
ingestion = IPFabricIngestionSerializer(read_only=True)
|
|
154
|
+
|
|
155
|
+
class Meta:
|
|
156
|
+
model = IPFabricIngestionIssue
|
|
157
|
+
fields = [
|
|
158
|
+
"id",
|
|
159
|
+
"ingestion",
|
|
160
|
+
"timestamp",
|
|
161
|
+
"model",
|
|
162
|
+
"message",
|
|
163
|
+
"raw_data",
|
|
164
|
+
"coalesce_fields",
|
|
165
|
+
"defaults",
|
|
166
|
+
"exception",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
|
|
148
170
|
class IPFabricSourceSerializer(NetBoxModelSerializer):
|
|
149
171
|
status = ChoiceField(choices=DataSourceStatusChoices)
|
|
150
172
|
url = serializers.URLField()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# api/urls.py
|
|
2
2
|
from netbox.api.routers import NetBoxRouter
|
|
3
3
|
|
|
4
|
+
from ipfabric_netbox.api.views import IPFabricIngestionIssueViewSet
|
|
4
5
|
from ipfabric_netbox.api.views import IPFabricIngestionViewSet
|
|
5
6
|
from ipfabric_netbox.api.views import IPFabricRelationshipFieldiewSet
|
|
6
7
|
from ipfabric_netbox.api.views import IPFabricSnapshotViewSet
|
|
@@ -18,6 +19,7 @@ router.register("transform-map-group", IPFabricTransformMapGroupViewSet)
|
|
|
18
19
|
router.register("transform-map", IPFabricTransformMapViewSet)
|
|
19
20
|
router.register("sync", IPFabricSyncViewSet)
|
|
20
21
|
router.register("ingestion", IPFabricIngestionViewSet)
|
|
22
|
+
router.register("ingestion-issues", IPFabricIngestionIssueViewSet)
|
|
21
23
|
router.register("transform-field", IPFabricTransformFieldiewSet)
|
|
22
24
|
router.register("relationship-field", IPFabricRelationshipFieldiewSet)
|
|
23
25
|
urlpatterns = router.urls
|
|
@@ -5,6 +5,7 @@ from rest_framework.decorators import action
|
|
|
5
5
|
from rest_framework.response import Response
|
|
6
6
|
from utilities.query import count_related
|
|
7
7
|
|
|
8
|
+
from .serializers import IPFabricIngestionIssueSerializer
|
|
8
9
|
from .serializers import IPFabricIngestionSerializer
|
|
9
10
|
from .serializers import IPFabricRelationshipFieldSerializer
|
|
10
11
|
from .serializers import IPFabricSnapshotSerializer
|
|
@@ -18,6 +19,7 @@ from ipfabric_netbox.filtersets import IPFabricSourceFilterSet
|
|
|
18
19
|
from ipfabric_netbox.filtersets import IPFabricTransformFieldFilterSet
|
|
19
20
|
from ipfabric_netbox.models import IPFabricData
|
|
20
21
|
from ipfabric_netbox.models import IPFabricIngestion
|
|
22
|
+
from ipfabric_netbox.models import IPFabricIngestionIssue
|
|
21
23
|
from ipfabric_netbox.models import IPFabricRelationshipField
|
|
22
24
|
from ipfabric_netbox.models import IPFabricSnapshot
|
|
23
25
|
from ipfabric_netbox.models import IPFabricSource
|
|
@@ -59,6 +61,11 @@ class IPFabricIngestionViewSet(NetBoxReadOnlyModelViewSet):
|
|
|
59
61
|
serializer_class = IPFabricIngestionSerializer
|
|
60
62
|
|
|
61
63
|
|
|
64
|
+
class IPFabricIngestionIssueViewSet(NetBoxReadOnlyModelViewSet):
|
|
65
|
+
queryset = IPFabricIngestionIssue.objects.all()
|
|
66
|
+
serializer_class = IPFabricIngestionIssueSerializer
|
|
67
|
+
|
|
68
|
+
|
|
62
69
|
class IPFabricSnapshotViewSet(NetBoxModelViewSet):
|
|
63
70
|
queryset = IPFabricSnapshot.objects.all()
|
|
64
71
|
serializer_class = IPFabricSnapshotSerializer
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from core.exceptions import SyncError
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .models import IPFabricIngestionIssue
|
|
7
|
+
from .models import IPFabricIngestion
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IngestionIssue(Exception):
|
|
11
|
+
"""
|
|
12
|
+
This exception is used to indicate an issue during the ingestion process.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Store created issue object ID if it exists for this exception
|
|
16
|
+
issue_id = None
|
|
17
|
+
model: str = ""
|
|
18
|
+
defaults: dict[str, str] = {}
|
|
19
|
+
coalesce_fields: dict[str, str] = {}
|
|
20
|
+
|
|
21
|
+
def __init__(self, model: str, data: dict, context: dict = None, issue_id=None):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.model = model
|
|
24
|
+
self.data = data
|
|
25
|
+
context = context or {}
|
|
26
|
+
self.defaults = context.pop("defaults", {})
|
|
27
|
+
self.coalesce_fields = context
|
|
28
|
+
self.issue_id = issue_id
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SearchError(IngestionIssue, LookupError):
|
|
32
|
+
def __init__(self, message: str, *args, **kwargs):
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
self.message = message
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return self.message
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SyncDataError(IngestionIssue, SyncError):
|
|
41
|
+
def __str__(self):
|
|
42
|
+
return f"Sync failed for {self.model}: coalesce_fields={self.coalesce_fields} defaults={self.defaults}."
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IPAddressDuplicateError(IngestionIssue, SyncError):
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return f"IP address {self.data.get('address')} already exists in {self.model} with coalesce_fields={self.coalesce_fields}."
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_or_get_sync_issue(
|
|
51
|
+
exception: Exception,
|
|
52
|
+
ingestion: "IPFabricIngestion",
|
|
53
|
+
message: str = None,
|
|
54
|
+
model: str = None,
|
|
55
|
+
context: dict = None,
|
|
56
|
+
data: dict = None,
|
|
57
|
+
) -> (bool, "IPFabricIngestionIssue"):
|
|
58
|
+
"""
|
|
59
|
+
Helper function to handle sync errors and create IPFabricIngestionIssue if needed.
|
|
60
|
+
"""
|
|
61
|
+
context = context or {}
|
|
62
|
+
|
|
63
|
+
# TODO: This is to prevent circular import issues, clean it up later.
|
|
64
|
+
from .models import IPFabricIngestionIssue
|
|
65
|
+
|
|
66
|
+
if not hasattr(exception, "issue_id") or not exception.issue_id:
|
|
67
|
+
issue = IPFabricIngestionIssue.objects.create(
|
|
68
|
+
ingestion=ingestion,
|
|
69
|
+
exception=exception.__class__.__name__,
|
|
70
|
+
message=message or getattr(exception, "message", str(exception)),
|
|
71
|
+
model=model,
|
|
72
|
+
coalesce_fields={k: v for k, v in context.items() if k not in ["defaults"]},
|
|
73
|
+
defaults=context.get("defaults", dict()),
|
|
74
|
+
raw_data=data or dict(),
|
|
75
|
+
)
|
|
76
|
+
if hasattr(exception, "issue_id"):
|
|
77
|
+
exception.issue_id = issue.id
|
|
78
|
+
return True, issue
|
|
79
|
+
else:
|
|
80
|
+
issue = IPFabricIngestionIssue.objects.get(id=exception.issue_id)
|
|
81
|
+
return False, issue
|
|
@@ -10,6 +10,7 @@ from netbox_branching.models import ChangeDiff
|
|
|
10
10
|
|
|
11
11
|
from .models import IPFabricData
|
|
12
12
|
from .models import IPFabricIngestion
|
|
13
|
+
from .models import IPFabricIngestionIssue
|
|
13
14
|
from .models import IPFabricSnapshot
|
|
14
15
|
from .models import IPFabricSource
|
|
15
16
|
from .models import IPFabricSync
|
|
@@ -37,6 +38,35 @@ class IPFabricIngestionChangeFilterSet(BaseFilterSet):
|
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
class IPFabricIngestionIssueFilterSet(BaseFilterSet):
|
|
42
|
+
q = django_filters.CharFilter(method="search")
|
|
43
|
+
|
|
44
|
+
class Meta:
|
|
45
|
+
model = IPFabricIngestionIssue
|
|
46
|
+
fields = [
|
|
47
|
+
"model",
|
|
48
|
+
"timestamp",
|
|
49
|
+
"raw_data",
|
|
50
|
+
"coalesce_fields",
|
|
51
|
+
"defaults",
|
|
52
|
+
"exception",
|
|
53
|
+
"message",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def search(self, queryset, name, value):
|
|
57
|
+
if not value.strip():
|
|
58
|
+
return queryset
|
|
59
|
+
return queryset.filter(
|
|
60
|
+
Q(model__icontains=value)
|
|
61
|
+
| Q(timestamp__icontains=value)
|
|
62
|
+
| Q(raw_data__icontains=value)
|
|
63
|
+
| Q(coalesce_fields__icontains=value)
|
|
64
|
+
| Q(defaults__icontains=value)
|
|
65
|
+
| Q(exception__icontains=value)
|
|
66
|
+
| Q(message__icontains=value)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
40
70
|
class IPFabricDataFilterSet(BaseFilterSet):
|
|
41
71
|
q = django_filters.CharFilter(method="search")
|
|
42
72
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Generated by Django 5.2 on 2025-07-11 19:39
|
|
2
|
+
import django.db.models.deletion
|
|
3
|
+
import django.utils.timezone
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
from django.db import models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
dependencies = [
|
|
10
|
+
(
|
|
11
|
+
"ipfabric_netbox",
|
|
12
|
+
"0014_ipfabrictransformmapgroup_ipfabrictransformmap_group",
|
|
13
|
+
),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="IPFabricIngestionIssue",
|
|
19
|
+
fields=[
|
|
20
|
+
(
|
|
21
|
+
"id",
|
|
22
|
+
models.BigAutoField(
|
|
23
|
+
auto_created=True, primary_key=True, serialize=False
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
("timestamp", models.DateTimeField(default=django.utils.timezone.now)),
|
|
27
|
+
("model", models.CharField(blank=True, max_length=100, null=True)),
|
|
28
|
+
("message", models.TextField()),
|
|
29
|
+
("raw_data", models.TextField(blank=True, default="")),
|
|
30
|
+
("coalesce_fields", models.TextField(blank=True, default="")),
|
|
31
|
+
("defaults", models.TextField(blank=True, default="")),
|
|
32
|
+
("exception", models.TextField()),
|
|
33
|
+
(
|
|
34
|
+
"ingestion",
|
|
35
|
+
models.ForeignKey(
|
|
36
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
37
|
+
related_name="issues",
|
|
38
|
+
to="ipfabric_netbox.ipfabricingestion",
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
options={
|
|
43
|
+
"verbose_name": "IP Fabric Ingestion Issue",
|
|
44
|
+
"verbose_name_plural": "IP Fabric Ingestion Issues",
|
|
45
|
+
"ordering": ["timestamp"],
|
|
46
|
+
},
|
|
47
|
+
),
|
|
48
|
+
]
|
|
@@ -236,6 +236,8 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
236
236
|
target_class = self.target_model.model_class()
|
|
237
237
|
queryset = target_class.objects.using(connection_name)
|
|
238
238
|
|
|
239
|
+
# Don't change context since it's used in case of exception for IPFabricIngestionIssue
|
|
240
|
+
context = deepcopy(context)
|
|
239
241
|
defaults = context.pop("defaults", {})
|
|
240
242
|
|
|
241
243
|
with transaction.atomic(using=connection_name):
|
|
@@ -967,6 +969,29 @@ class IPFabricIngestion(JobsMixin, models.Model):
|
|
|
967
969
|
)
|
|
968
970
|
|
|
969
971
|
|
|
972
|
+
class IPFabricIngestionIssue(models.Model):
|
|
973
|
+
objects = RestrictedQuerySet.as_manager()
|
|
974
|
+
|
|
975
|
+
ingestion = models.ForeignKey(
|
|
976
|
+
to="IPFabricIngestion", on_delete=models.CASCADE, related_name="issues"
|
|
977
|
+
)
|
|
978
|
+
timestamp = models.DateTimeField(default=timezone.now)
|
|
979
|
+
model = models.CharField(max_length=100, blank=True, null=True)
|
|
980
|
+
message = models.TextField()
|
|
981
|
+
raw_data = models.TextField(blank=True, default="")
|
|
982
|
+
coalesce_fields = models.TextField(blank=True, default="")
|
|
983
|
+
defaults = models.TextField(blank=True, default="")
|
|
984
|
+
exception = models.TextField()
|
|
985
|
+
|
|
986
|
+
class Meta:
|
|
987
|
+
ordering = ["timestamp"]
|
|
988
|
+
verbose_name = "IP Fabric Ingestion Issue"
|
|
989
|
+
verbose_name_plural = "IP Fabric Ingestion Issues"
|
|
990
|
+
|
|
991
|
+
def __str__(self):
|
|
992
|
+
return f"[{self.timestamp}] {self.message}"
|
|
993
|
+
|
|
994
|
+
|
|
970
995
|
class IPFabricData(models.Model):
|
|
971
996
|
snapshot_data = models.ForeignKey(
|
|
972
997
|
to=IPFabricSnapshot,
|
|
@@ -8,6 +8,7 @@ from netbox_branching.models import ChangeDiff
|
|
|
8
8
|
|
|
9
9
|
from .models import IPFabricData
|
|
10
10
|
from .models import IPFabricIngestion
|
|
11
|
+
from .models import IPFabricIngestionIssue
|
|
11
12
|
from .models import IPFabricRelationshipField
|
|
12
13
|
from .models import IPFabricSnapshot
|
|
13
14
|
from .models import IPFabricSource
|
|
@@ -199,6 +200,28 @@ class IPFabricIngestionChangesTable(NetBoxTable):
|
|
|
199
200
|
default_columns = ("object", "action", "object_type", "actions")
|
|
200
201
|
|
|
201
202
|
|
|
203
|
+
class IPFabricIngestionIssuesTable(NetBoxTable):
|
|
204
|
+
id = tables.Column(verbose_name=_("ID"))
|
|
205
|
+
exception = tables.Column(verbose_name="Exception Type")
|
|
206
|
+
message = tables.Column(verbose_name="Error Message")
|
|
207
|
+
actions = None
|
|
208
|
+
|
|
209
|
+
class Meta(NetBoxTable.Meta):
|
|
210
|
+
model = IPFabricIngestionIssue
|
|
211
|
+
fields = (
|
|
212
|
+
"model",
|
|
213
|
+
"timestamp",
|
|
214
|
+
"raw_data",
|
|
215
|
+
"coalesce_fields",
|
|
216
|
+
"defaults",
|
|
217
|
+
"exception",
|
|
218
|
+
"message",
|
|
219
|
+
)
|
|
220
|
+
default_columns = ("model", "exception", "message")
|
|
221
|
+
empty_text = _("No Ingestion Issues found")
|
|
222
|
+
order_by = "id"
|
|
223
|
+
|
|
224
|
+
|
|
202
225
|
class DeviceIPFTable(tables.Table):
|
|
203
226
|
hostname = Column()
|
|
204
227
|
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
</div>
|
|
37
37
|
{% endblock control-buttons %}
|
|
38
38
|
|
|
39
|
+
{% block tabs %}
|
|
40
|
+
<div id="object_tabs" hx-swap-oob="true">
|
|
41
|
+
{% include 'ipfabric_netbox/partials/object_tabs.html' with object=object %}
|
|
42
|
+
</div>
|
|
43
|
+
{% endblock tabs %}
|
|
39
44
|
|
|
40
45
|
{% block content %}
|
|
41
46
|
<div class="row mb-3">
|
|
@@ -70,7 +75,6 @@
|
|
|
70
75
|
<th scope="row">Ingestion Status</th>
|
|
71
76
|
<td><div id="ingestion_status" hx-swap-oob="true">{% include 'ipfabric_netbox/partials/ingestion_status.html' with object=object %}</div></td>
|
|
72
77
|
<!-- <td >{% badge object.sync.get_status_display bg_color=object.sync.get_status_color %}</td> -->
|
|
73
|
-
</div>
|
|
74
78
|
</tr>
|
|
75
79
|
<tr>
|
|
76
80
|
<th scope="row">Snapshot</th>
|
|
@@ -88,59 +92,26 @@
|
|
|
88
92
|
{% plugin_left_page object %}
|
|
89
93
|
</div>
|
|
90
94
|
<div class="col col-md-6">
|
|
91
|
-
<div
|
|
92
|
-
|
|
93
|
-
<div class="card-body">
|
|
94
|
-
<table class="table table-hover attr-table">
|
|
95
|
-
<tr>
|
|
96
|
-
<th scope="row">Created</th>
|
|
97
|
-
<td>
|
|
98
|
-
{% if object.num_created %}
|
|
99
|
-
{{ object.num_created }}
|
|
100
|
-
{% else %}
|
|
101
|
-
{{ ''|placeholder }}
|
|
102
|
-
{% endif %}
|
|
103
|
-
</td>
|
|
104
|
-
</tr>
|
|
105
|
-
<tr>
|
|
106
|
-
<th scope="row">Updated</th>
|
|
107
|
-
<td>
|
|
108
|
-
{% if object.num_updated %}
|
|
109
|
-
{{ object.num_updated }}
|
|
110
|
-
{% else %}
|
|
111
|
-
{{ ''|placeholder }}
|
|
112
|
-
{% endif %}
|
|
113
|
-
</td>
|
|
114
|
-
</tr>
|
|
115
|
-
<tr>
|
|
116
|
-
<th scope="row">Deleted</th>
|
|
117
|
-
<td>
|
|
118
|
-
{% if object.num_deleted %}
|
|
119
|
-
{{ object.num_deleted }}
|
|
120
|
-
{% else %}
|
|
121
|
-
{{ ''|placeholder }}
|
|
122
|
-
{% endif %}
|
|
123
|
-
</td>
|
|
124
|
-
</tr>
|
|
125
|
-
</table>
|
|
126
|
-
</div>
|
|
95
|
+
<div id="ingestion_statistics" hx-swap-oob="true">
|
|
96
|
+
{% include 'ipfabric_netbox/partials/ingestion_statistics.html' with object=object %}
|
|
127
97
|
</div>
|
|
128
|
-
<div id="ingestion_progress">
|
|
98
|
+
<div id="ingestion_progress" hx-swap-oob="true">
|
|
129
99
|
{% include 'ipfabric_netbox/partials/ingestion_progress.html' %}
|
|
130
100
|
</div>
|
|
131
101
|
<!-- {% include 'inc/panels/related_objects.html' %} -->
|
|
132
102
|
{% include 'inc/panels/custom_fields.html' %}
|
|
133
103
|
{% plugin_right_page object %}
|
|
134
104
|
</div>
|
|
135
|
-
|
|
136
|
-
<div class="
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
105
|
+
<div class="row mb-3">
|
|
106
|
+
<div class="col col-md-12" {% if not object.job.completed %} hx-get="{% url 'plugins:ipfabric_netbox:ipfabricingestion_logs' pk=object.pk %}?type=info"
|
|
107
|
+
hx-trigger="every 5s" hx-target="#ingestion_logs" hx-select="#ingestion_logs" hx-select-oob="#ingestion_status:innerHTML,#ingestion_progress:innerHTML,#ingestion_statistics:innerHTML,#object_tabs:innerHTML"
|
|
108
|
+
hx-swap="innerHTML" {% endif %}>
|
|
109
|
+
<div id="ingestion_logs">{% include 'ipfabric_netbox/partials/job_logs.html' with job=object.job %}</div>
|
|
110
|
+
</div>
|
|
141
111
|
</div>
|
|
112
|
+
</div>
|
|
142
113
|
{% endblock content %}
|
|
143
114
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
115
|
+
{% block modals %}
|
|
116
|
+
{% include 'inc/htmx_modal.html' with size='lg' %}
|
|
117
|
+
{% endblock modals %}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<div id="object_tabs" hx-swap-oob="true">
|
|
2
|
+
{% include 'ipfabric_netbox/partials/object_tabs.html' with object=object %}
|
|
3
|
+
</div>
|
|
4
|
+
<div id="ingestion_statistics" hx-swap-oob="true">
|
|
5
|
+
{% include 'ipfabric_netbox/partials/ingestion_statistics.html' with object=object %}
|
|
6
|
+
</div>
|
|
2
7
|
<div id="ingestion_status" hx-swap-oob="true">
|
|
3
8
|
{% include 'ipfabric_netbox/partials/ingestion_status.html' with object=object %}
|
|
4
9
|
</div>
|
ipfabric_netbox-4.1.0b5/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h5 class="card-header">Statistics</h5>
|
|
3
|
+
<div class="card-body">
|
|
4
|
+
<table class="table table-hover attr-table">
|
|
5
|
+
<tr>
|
|
6
|
+
<th scope="row">Created</th>
|
|
7
|
+
<td>
|
|
8
|
+
{% if object.num_created %}
|
|
9
|
+
{{ object.num_created }}
|
|
10
|
+
{% else %}
|
|
11
|
+
{{ ''|placeholder }}
|
|
12
|
+
{% endif %}
|
|
13
|
+
</td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<th scope="row">Updated</th>
|
|
17
|
+
<td>
|
|
18
|
+
{% if object.num_updated %}
|
|
19
|
+
{{ object.num_updated }}
|
|
20
|
+
{% else %}
|
|
21
|
+
{{ ''|placeholder }}
|
|
22
|
+
{% endif %}
|
|
23
|
+
</td>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<th scope="row">Deleted</th>
|
|
27
|
+
<td>
|
|
28
|
+
{% if object.num_deleted %}
|
|
29
|
+
{{ object.num_deleted }}
|
|
30
|
+
{% else %}
|
|
31
|
+
{{ ''|placeholder }}
|
|
32
|
+
{% endif %}
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
</table>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
ipfabric_netbox-4.1.0b5/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% badge object.sync.get_status_display bg_color=object.sync.get_status_color %} {% if object.errors and object.errors.count %}(with {{ object.errors.count }} issues){% endif %}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{% load helpers %}
|
|
2
|
+
{% load tabs %}
|
|
3
|
+
|
|
4
|
+
<ul class="nav nav-tabs" role="presentation">
|
|
5
|
+
{# Primary tab #}
|
|
6
|
+
<li class="nav-item">
|
|
7
|
+
<a class="nav-link{% if not tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
|
8
|
+
</li>
|
|
9
|
+
|
|
10
|
+
{# Include tabs for registered model views #}
|
|
11
|
+
{% model_view_tabs object %}
|
|
12
|
+
</ul>
|