ipfabric_netbox 4.3.2b9__py3-none-any.whl → 4.3.2b11__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.

Potentially problematic release.


This version of ipfabric_netbox might be problematic. Click here for more details.

Files changed (50) hide show
  1. ipfabric_netbox/__init__.py +1 -1
  2. ipfabric_netbox/api/serializers.py +112 -7
  3. ipfabric_netbox/api/urls.py +6 -0
  4. ipfabric_netbox/api/views.py +23 -0
  5. ipfabric_netbox/choices.py +74 -40
  6. ipfabric_netbox/data/endpoint.json +52 -0
  7. ipfabric_netbox/data/filters.json +51 -0
  8. ipfabric_netbox/data/transform_map.json +190 -176
  9. ipfabric_netbox/exceptions.py +7 -5
  10. ipfabric_netbox/filtersets.py +310 -41
  11. ipfabric_netbox/forms.py +330 -80
  12. ipfabric_netbox/graphql/__init__.py +6 -0
  13. ipfabric_netbox/graphql/enums.py +5 -5
  14. ipfabric_netbox/graphql/filters.py +56 -4
  15. ipfabric_netbox/graphql/schema.py +28 -0
  16. ipfabric_netbox/graphql/types.py +61 -1
  17. ipfabric_netbox/jobs.py +12 -1
  18. ipfabric_netbox/migrations/0022_prepare_for_filters.py +182 -0
  19. ipfabric_netbox/migrations/0023_populate_filters_data.py +303 -0
  20. ipfabric_netbox/migrations/0024_finish_filters.py +29 -0
  21. ipfabric_netbox/migrations/0025_add_vss_chassis_endpoint.py +166 -0
  22. ipfabric_netbox/models.py +432 -17
  23. ipfabric_netbox/navigation.py +98 -24
  24. ipfabric_netbox/tables.py +194 -9
  25. ipfabric_netbox/templates/ipfabric_netbox/htmx_list.html +5 -0
  26. ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions.html +59 -0
  27. ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions_content.html +39 -0
  28. ipfabric_netbox/templates/ipfabric_netbox/inc/endpoint_filters_with_selector.html +54 -0
  29. ipfabric_netbox/templates/ipfabric_netbox/ipfabricendpoint.html +39 -0
  30. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilter.html +51 -0
  31. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression.html +39 -0
  32. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression_edit.html +150 -0
  33. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
  34. ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +16 -2
  35. ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +68 -0
  36. ipfabric_netbox/tests/api/test_api.py +333 -13
  37. ipfabric_netbox/tests/test_filtersets.py +2592 -0
  38. ipfabric_netbox/tests/test_forms.py +1349 -74
  39. ipfabric_netbox/tests/test_models.py +242 -34
  40. ipfabric_netbox/tests/test_views.py +2031 -26
  41. ipfabric_netbox/urls.py +35 -0
  42. ipfabric_netbox/utilities/endpoint.py +83 -0
  43. ipfabric_netbox/utilities/filters.py +88 -0
  44. ipfabric_netbox/utilities/ipfutils.py +393 -377
  45. ipfabric_netbox/utilities/logging.py +7 -7
  46. ipfabric_netbox/utilities/transform_map.py +144 -5
  47. ipfabric_netbox/views.py +719 -5
  48. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/METADATA +2 -2
  49. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/RECORD +50 -33
  50. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/WHEEL +1 -1
@@ -3,29 +3,17 @@ from netbox.plugins import PluginMenu
3
3
  from netbox.plugins import PluginMenuButton
4
4
  from netbox.plugins import PluginMenuItem
5
5
 
6
-
7
- sync_buttons = [
8
- PluginMenuButton(
9
- link="plugins:ipfabric_netbox:ipfabricsync_add",
10
- title=_("Add"),
11
- icon_class="mdi mdi-plus-thick",
12
- permissions=["ipfabric_netbox.add_ipfabricsync"],
13
- )
14
- ]
15
-
16
- source_buttons = [
17
- PluginMenuButton(
18
- link="plugins:ipfabric_netbox:ipfabricsource_add",
19
- title=_("Add"),
20
- icon_class="mdi mdi-plus-thick",
21
- permissions=["ipfabric_netbox.add_ipfabricsource"],
22
- )
23
- ]
24
-
25
6
  source = PluginMenuItem(
26
7
  link="plugins:ipfabric_netbox:ipfabricsource_list",
27
8
  link_text=_("Sources"),
28
- buttons=source_buttons,
9
+ buttons=[
10
+ PluginMenuButton(
11
+ link="plugins:ipfabric_netbox:ipfabricsource_add",
12
+ title=_("Add"),
13
+ icon_class="mdi mdi-plus-thick",
14
+ permissions=["ipfabric_netbox.add_ipfabricsource"],
15
+ )
16
+ ],
29
17
  permissions=["ipfabric_netbox.view_ipfabricsource"],
30
18
  )
31
19
 
@@ -35,14 +23,62 @@ snapshot = PluginMenuItem(
35
23
  permissions=["ipfabric_netbox.view_ipfabricsnapshot"],
36
24
  )
37
25
 
38
-
39
- ingestion = PluginMenuItem(
26
+ sync = PluginMenuItem(
40
27
  link="plugins:ipfabric_netbox:ipfabricsync_list",
41
28
  link_text=_("Syncs"),
42
- buttons=sync_buttons,
29
+ buttons=[
30
+ PluginMenuButton(
31
+ link="plugins:ipfabric_netbox:ipfabricsync_add",
32
+ title=_("Add"),
33
+ icon_class="mdi mdi-plus-thick",
34
+ permissions=["ipfabric_netbox.add_ipfabricsync"],
35
+ )
36
+ ],
43
37
  permissions=["ipfabric_netbox.view_ipfabricsync"],
44
38
  )
45
39
 
40
+ ingestion = PluginMenuItem(
41
+ link="plugins:ipfabric_netbox:ipfabricingestion_list",
42
+ link_text=_("Ingestions"),
43
+ buttons=[],
44
+ permissions=["ipfabric_netbox.view_ipfabricingestion"],
45
+ )
46
+
47
+ endpoint = PluginMenuItem(
48
+ link="plugins:ipfabric_netbox:ipfabricendpoint_list",
49
+ link_text=_("Endpoints"),
50
+ permissions=["ipfabric_netbox.view_ipfabricendpoint"],
51
+ buttons=[],
52
+ )
53
+
54
+ filter = PluginMenuItem(
55
+ link="plugins:ipfabric_netbox:ipfabricfilter_list",
56
+ link_text=_("Filters"),
57
+ permissions=["ipfabric_netbox.view_ipfabricfilter"],
58
+ buttons=[
59
+ PluginMenuButton(
60
+ link="plugins:ipfabric_netbox:ipfabricfilter_add",
61
+ title=_("Add"),
62
+ icon_class="mdi mdi-plus-thick",
63
+ permissions=["ipfabric_netbox.add_ipfabricfilter"],
64
+ )
65
+ ],
66
+ )
67
+
68
+ filter_expression = PluginMenuItem(
69
+ link="plugins:ipfabric_netbox:ipfabricfilterexpression_list",
70
+ link_text=_("Filter Expressions"),
71
+ permissions=["ipfabric_netbox.view_ipfabricfilterexpression"],
72
+ buttons=[
73
+ PluginMenuButton(
74
+ link="plugins:ipfabric_netbox:ipfabricfilterexpression_add",
75
+ title=_("Add"),
76
+ icon_class="mdi mdi-plus-thick",
77
+ permissions=["ipfabric_netbox.add_ipfabricfilterexpression"],
78
+ )
79
+ ],
80
+ )
81
+
46
82
  tmg = PluginMenuItem(
47
83
  link="plugins:ipfabric_netbox:ipfabrictransformmapgroup_list",
48
84
  link_text=_("Transform Map Groups"),
@@ -70,8 +106,46 @@ tm = PluginMenuItem(
70
106
  )
71
107
  ],
72
108
  )
109
+
110
+ tmf = PluginMenuItem(
111
+ link="plugins:ipfabric_netbox:ipfabrictransformfield_list",
112
+ link_text=_("Transform Fields"),
113
+ permissions=["ipfabric_netbox.view_ipfabrictransformfield"],
114
+ buttons=[
115
+ PluginMenuButton(
116
+ link="plugins:ipfabric_netbox:ipfabrictransformfield_add",
117
+ title=_("Add"),
118
+ icon_class="mdi mdi-plus-thick",
119
+ permissions=["ipfabric_netbox.add_ipfabrictransformfield"],
120
+ )
121
+ ],
122
+ )
123
+
124
+ tmr = PluginMenuItem(
125
+ link="plugins:ipfabric_netbox:ipfabricrelationshipfield_list",
126
+ link_text=_("Relationship Fields"),
127
+ permissions=["ipfabric_netbox.view_ipfabricrelationshipfield"],
128
+ buttons=[
129
+ PluginMenuButton(
130
+ link="plugins:ipfabric_netbox:ipfabricrelationshipfield_add",
131
+ title=_("Add"),
132
+ icon_class="mdi mdi-plus-thick",
133
+ permissions=["ipfabric_netbox.add_ipfabricrelationshipfield"],
134
+ )
135
+ ],
136
+ )
137
+
73
138
  menu = PluginMenu(
74
139
  label="IP Fabric",
75
140
  icon_class="mdi mdi-cloud-sync",
76
- groups=(("IP Fabric", (source, snapshot, ingestion, tmg, tm)),),
141
+ groups=(
142
+ (
143
+ "Data Sync",
144
+ (source, snapshot, sync, ingestion),
145
+ ),
146
+ (
147
+ "Configuration",
148
+ (endpoint, filter, filter_expression, tmg, tm, tmf, tmr),
149
+ ),
150
+ ),
77
151
  )
ipfabric_netbox/tables.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import django_tables2 as tables
2
+ from django.db.models import Count
2
3
  from django.utils.html import format_html
3
4
  from django.utils.translation import gettext_lazy as _
4
5
  from django_tables2 import Column
@@ -7,6 +8,9 @@ from netbox.tables import NetBoxTable
7
8
  from netbox_branching.models import ChangeDiff
8
9
 
9
10
  from .models import IPFabricData
11
+ from .models import IPFabricEndpoint
12
+ from .models import IPFabricFilter
13
+ from .models import IPFabricFilterExpression
10
14
  from .models import IPFabricIngestion
11
15
  from .models import IPFabricIngestionIssue
12
16
  from .models import IPFabricRelationshipField
@@ -42,25 +46,126 @@ DATA_BUTTON = """
42
46
  </a>
43
47
  """
44
48
 
49
+ COMBINED_EXPRESSIONS_BUTTON = """
50
+ <a href="#"
51
+ hx-get="{% url 'plugins:ipfabric_netbox:ipfabricfilter_combined_expressions' pk=record.pk %}"
52
+ hx-target="#htmx-modal-content"
53
+ data-bs-toggle="modal"
54
+ data-bs-target="#htmx-modal"
55
+ class="btn btn-success btn-sm"
56
+ >
57
+ Combined
58
+ </a>
59
+ """
60
+
61
+ ENDPOINT_FILTERS_BUTTON = """
62
+ {% if record.sync_pk %}
63
+ <a href="#"
64
+ hx-get="{% url 'plugins:ipfabric_netbox:ipfabricendpoint_filters' pk=record.pk %}?sync_pk={{ record.sync_pk }}&from_sync=true"
65
+ hx-target="#htmx-modal-content"
66
+ data-bs-toggle="modal"
67
+ data-bs-target="#htmx-modal"
68
+ class="btn btn-success btn-sm"
69
+ >
70
+ Show Filters
71
+ </a>
72
+ {% else %}
73
+ <a href="#"
74
+ hx-get="{% url 'plugins:ipfabric_netbox:ipfabricendpoint_filters' pk=record.pk %}"
75
+ hx-target="#htmx-modal-content"
76
+ data-bs-toggle="modal"
77
+ data-bs-target="#htmx-modal"
78
+ class="btn btn-success btn-sm"
79
+ >
80
+ Show Filters
81
+ </a>
82
+ {% endif %}
83
+ """
84
+
85
+
86
+ class IPFabricEndpointTable(NetBoxTable):
87
+ name = tables.Column(linkify=True)
88
+ endpoint = tables.Column(verbose_name=_("Endpoint"))
89
+ filters_count = tables.Column(
90
+ verbose_name=_("Filters"),
91
+ empty_values=(),
92
+ default="",
93
+ linkify=lambda record: f"/plugins/ipfabric/filter/?endpoint_id={record.pk}",
94
+ )
95
+ show_filters = columns.TemplateColumn(
96
+ template_code=ENDPOINT_FILTERS_BUTTON,
97
+ orderable=False,
98
+ verbose_name=_("Combined Filters"),
99
+ empty_values=(),
100
+ default="",
101
+ )
102
+ actions = None
103
+
104
+ def render_filters_count(self, record):
105
+ """Only render filter count if it's available."""
106
+ if not hasattr(record, "filters_count"):
107
+ return ""
108
+ return record.filters_count
109
+
110
+ class Meta(NetBoxTable.Meta):
111
+ model = IPFabricEndpoint
112
+ fields = ("name", "description", "endpoint", "filters_count", "show_filters")
113
+ default_columns = (
114
+ "name",
115
+ "description",
116
+ "endpoint",
117
+ "filters_count",
118
+ "show_filters",
119
+ )
120
+
45
121
 
46
122
  class IPFabricRelationshipFieldTable(NetBoxTable):
47
- actions = columns.ActionsColumn(actions=("edit", "delete"))
123
+ id = tables.Column()
124
+ transform_map = tables.Column(linkify=True)
48
125
  source_model = columns.ContentTypeColumn(verbose_name=_("Source Model"))
126
+ actions = columns.ActionsColumn(actions=("edit", "delete"))
49
127
 
50
128
  class Meta(NetBoxTable.Meta):
51
129
  model = IPFabricRelationshipField
52
- fields = ("source_model", "target_field", "coalesce", "actions")
53
- default_columns = ("source_model", "target_field", "coalesce", "actions")
130
+ fields = (
131
+ "id",
132
+ "transform_map",
133
+ "source_model",
134
+ "target_field",
135
+ "coalesce",
136
+ "actions",
137
+ )
138
+ default_columns = (
139
+ "transform_map",
140
+ "source_model",
141
+ "target_field",
142
+ "coalesce",
143
+ "actions",
144
+ )
54
145
 
55
146
 
56
147
  class IPFabricTransformFieldTable(NetBoxTable):
57
148
  id = tables.Column()
149
+ transform_map = tables.Column(linkify=True)
58
150
  actions = columns.ActionsColumn(actions=("edit", "delete"))
59
151
 
60
152
  class Meta(NetBoxTable.Meta):
61
153
  model = IPFabricTransformField
62
- fields = ("id", "source_field", "target_field", "coalesce", "actions")
63
- default_columns = ("source_field", "target_field", "coalesce", "actions")
154
+ fields = (
155
+ "id",
156
+ "transform_map",
157
+ "source_field",
158
+ "target_field",
159
+ "coalesce",
160
+ "actions",
161
+ )
162
+ default_columns = (
163
+ "transform_map",
164
+ "source_field",
165
+ "target_field",
166
+ "coalesce",
167
+ "actions",
168
+ )
64
169
 
65
170
 
66
171
  class IPFabricTransformMapGroupTable(NetBoxTable):
@@ -79,13 +184,47 @@ class IPFabricTransformMapGroupTable(NetBoxTable):
79
184
 
80
185
  class IPFabricTransformMapTable(NetBoxTable):
81
186
  name = tables.Column(linkify=True)
187
+ parents = tables.Column(linkify=False, verbose_name=_("Hierarchical Parents"))
82
188
  group = tables.Column(linkify=True)
189
+ source_endpoint = tables.Column(linkify=True)
83
190
  target_model = columns.ContentTypeColumn(verbose_name=_("Target Model"))
84
191
 
192
+ def render_parents(self, record):
193
+ """Render multiple parents as comma-separated list with links."""
194
+ parents = record.parents.all()
195
+ if not parents.exists():
196
+ return "---"
197
+
198
+ parent_links = []
199
+ for parent in parents:
200
+ url = parent.get_absolute_url()
201
+ parent_links.append(format_html('<a href="{}">{}</a>', url, parent.name))
202
+
203
+ # Join the SafeStrings directly
204
+ return format_html(", ".join(str(link) for link in parent_links))
205
+
206
+ def order_parents(self, queryset, is_descending):
207
+ """Order by count of parents."""
208
+
209
+ queryset = queryset.annotate(parent_count=Count("parents"))
210
+
211
+ if is_descending:
212
+ queryset = queryset.order_by("-parent_count")
213
+ else:
214
+ queryset = queryset.order_by("parent_count")
215
+
216
+ return queryset, True
217
+
85
218
  class Meta(NetBoxTable.Meta):
86
219
  model = IPFabricTransformMap
87
- fields = ("name", "group", "source_model", "target_model")
88
- default_columns = ("name", "group", "source_model", "target_model")
220
+ fields = ("name", "parents", "group", "source_endpoint", "target_model")
221
+ default_columns = (
222
+ "name",
223
+ "parents",
224
+ "group",
225
+ "source_endpoint",
226
+ "target_model",
227
+ )
89
228
 
90
229
 
91
230
  class IPFabricIngestionTable(NetBoxTable):
@@ -107,8 +246,8 @@ class IPFabricIngestionTable(NetBoxTable):
107
246
 
108
247
  class Meta(NetBoxTable.Meta):
109
248
  model = IPFabricIngestion
110
- fields = ("name", "sync", "branch", "description", "user", "changes")
111
- default_columns = ("name", "sync", "branch", "description", "user", "changes")
249
+ fields = ("name", "sync", "branch", "user", "changes")
250
+ default_columns = ("name", "sync", "branch", "user", "changes")
112
251
 
113
252
 
114
253
  class IPFabricSnapshotTable(NetBoxTable):
@@ -257,6 +396,52 @@ class IPFabricIngestionIssuesTable(NetBoxTable):
257
396
  order_by = "id"
258
397
 
259
398
 
399
+ class IPFabricFilterTable(NetBoxTable):
400
+ name = tables.Column(linkify=True)
401
+ endpoints = tables.ManyToManyColumn(linkify_item=True)
402
+ filter_type = columns.ChoiceFieldColumn(verbose_name=_("Filter Type"))
403
+ syncs = tables.ManyToManyColumn(linkify_item=True)
404
+ expressions = columns.ManyToManyColumn(
405
+ linkify_item=True, verbose_name=_("Filter Expressions")
406
+ )
407
+ combined = columns.TemplateColumn(
408
+ template_code=COMBINED_EXPRESSIONS_BUTTON,
409
+ orderable=False,
410
+ verbose_name=_("Combined"),
411
+ )
412
+
413
+ class Meta(NetBoxTable.Meta):
414
+ model = IPFabricFilter
415
+ fields = (
416
+ "name",
417
+ "description",
418
+ "endpoints",
419
+ "filter_type",
420
+ "syncs",
421
+ "expressions",
422
+ "combined",
423
+ )
424
+ default_columns = (
425
+ "name",
426
+ "endpoints",
427
+ "filter_type",
428
+ "syncs",
429
+ "expressions",
430
+ "combined",
431
+ )
432
+
433
+
434
+ class IPFabricFilterExpressionTable(NetBoxTable):
435
+ name = tables.Column(linkify=True)
436
+ expression = tables.Column(verbose_name=_("Filter Expression"))
437
+ filters = tables.ManyToManyColumn(linkify_item=True)
438
+
439
+ class Meta(NetBoxTable.Meta):
440
+ model = IPFabricFilterExpression
441
+ fields = ("name", "description", "filters", "expression")
442
+ default_columns = ("name", "filters", "expression")
443
+
444
+
260
445
  class DeviceIPFTable(tables.Table):
261
446
  hostname = Column()
262
447
 
@@ -0,0 +1,5 @@
1
+ {% extends 'generic/object_list.html' %}
2
+
3
+ {% block modals %}
4
+ {% include 'inc/htmx_modal.html' %}
5
+ {% endblock modals %}
@@ -0,0 +1,59 @@
1
+ {% load i18n %}
2
+ <div class="modal-content" id="htmx-modal-content">
3
+ <div class="modal-header">
4
+ <h5 class="modal-title">
5
+ {% if context_type == "endpoint" %}
6
+ {% blocktrans %}Combined Filters for Endpoint: {{ object.name }}{% endblocktrans %}
7
+ <br>
8
+ <small class="text-muted">
9
+ {% trans "Endpoint:" %} {{ object.endpoint }} |
10
+ {% trans "Sync:" %} {{ sync.name }} (ID: {{ sync.pk }})
11
+ </small>
12
+ {% elif context_type == "endpoint_all" %}
13
+ {% blocktrans %}All Filters for Endpoint: {{ object.name }}{% endblocktrans %}
14
+ <br>
15
+ <small class="text-muted">
16
+ {% trans "Endpoint:" %} {{ object.endpoint }}
17
+ </small>
18
+ {% else %}
19
+ {% blocktrans %}Combined Expressions - {{ object.name }}{% endblocktrans %}
20
+ <small class="text-muted">(ID: {{ object.pk }})</small>
21
+ {% endif %}
22
+ </h5>
23
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
24
+ </div>
25
+ <div class="modal-body">
26
+ <div class="row mb-3">
27
+ <div class="col-12">
28
+ <div class="card">
29
+ <h5 class="card-header">
30
+ {% if context_type == "endpoint" or context_type == "endpoint_all" %}
31
+ {% trans "Combined Filters" %}: {{ object.endpoint }}
32
+ {% else %}
33
+ {% trans "Merged Filter Expressions for filter type" %}: {{ object.filter_type|upper }}
34
+ {% endif %}
35
+ </h5>
36
+ <div class="card-body">
37
+ {% if is_empty %}
38
+ <div class="alert alert-info" role="alert">
39
+ <i class="mdi mdi-information"></i>
40
+ {% if context_type == "endpoint" %}
41
+ {% trans "No filters are applied to this endpoint in this sync." %}
42
+ {% elif context_type == "endpoint_all" %}
43
+ {% trans "No filters are using this endpoint." %}
44
+ {% else %}
45
+ {% trans "No expressions defined for this filter." %}
46
+ {% endif %}
47
+ </div>
48
+ {% else %}
49
+ <pre>{{ combined_expressions | json }}</pre>
50
+ {% endif %}
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="modal-footer">
58
+ </div>
59
+ </div>
@@ -0,0 +1,39 @@
1
+ {% load i18n %}
2
+ <div class="row mb-3">
3
+ <div class="col-12">
4
+ <div class="card">
5
+ <h5 class="card-header">
6
+ {% if object %}
7
+ {% if context_type == "endpoint" or context_type == "endpoint_all" %}
8
+ {% trans "Combined Filters" %}: {{ object.endpoint }}
9
+ {% if sync %}
10
+ <small class="text-muted">&nbsp;- {% trans "Sync:" %} {{ sync.name }} (ID: {{ sync.pk }})</small>
11
+ {% else %}
12
+ <small class="text-muted">&nbsp;- {% trans "All Syncs" %}</small>
13
+ {% endif %}
14
+ {% else %}
15
+ {% trans "Merged Filter Expressions for filter type" %}: {{ object.filter_type|upper }}
16
+ {% endif %}
17
+ {% else %}
18
+ {% trans "Combined Filters" %}
19
+ {% endif %}
20
+ </h5>
21
+ <div class="card-body">
22
+ {% if is_empty %}
23
+ <div class="alert alert-info" role="alert">
24
+ <i class="mdi mdi-information"></i>
25
+ {% if context_type == "endpoint" %}
26
+ {% trans "No filters are applied to this endpoint in this sync." %}
27
+ {% elif context_type == "endpoint_all" %}
28
+ {% trans "No filters are using this endpoint." %}
29
+ {% else %}
30
+ {% trans "No expressions defined for this filter." %}
31
+ {% endif %}
32
+ </div>
33
+ {% else %}
34
+ <pre>{{ combined_expressions | json }}</pre>
35
+ {% endif %}
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
@@ -0,0 +1,54 @@
1
+ {% load i18n %}
2
+ <div class="modal-content" id="htmx-modal-content">
3
+ <div class="modal-header">
4
+ <h5 class="modal-title">
5
+ {% if object %}
6
+ {% blocktrans %}Filters for Endpoint: {{ object.name }}{% endblocktrans %}
7
+ <br>
8
+ <small class="text-muted">
9
+ {% trans "Endpoint:" %} {{ object.endpoint }}
10
+ </small>
11
+ {% else %}
12
+ {% trans "Endpoint Filters" %}
13
+ {% endif %}
14
+ </h5>
15
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
16
+ </div>
17
+ <div class="modal-body">
18
+ {% if object %}
19
+ <div class="row mb-3">
20
+ <div class="col-12">
21
+ <div class="form-group">
22
+ <label for="sync-selector">{% trans "Select Sync:" %}</label>
23
+ <select id="sync-selector"
24
+ class="form-select"
25
+ hx-get="{% url 'plugins:ipfabric_netbox:ipfabricendpoint_filters' pk=object.pk %}"
26
+ hx-target="#filters-content"
27
+ hx-include="#sync-selector"
28
+ hx-swap="innerHTML"
29
+ name="sync_pk">
30
+ <option value="">{% trans "All Syncs" %}</option>
31
+ {% for sync in available_syncs %}
32
+ <option value="{{ sync.pk }}" {% if selected_sync_pk == sync.pk %}selected{% endif %}>
33
+ {{ sync.name }} (ID: {{ sync.pk }})
34
+ </option>
35
+ {% endfor %}
36
+ </select>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <div id="filters-content">
42
+ {% include "ipfabric_netbox/inc/combined_expressions_content.html" %}
43
+ </div>
44
+ {% else %}
45
+ <div class="alert alert-info" role="alert">
46
+ <i class="mdi mdi-information"></i>
47
+ {% trans "This view requires an HTMX request." %}
48
+ </div>
49
+ {% endif %}
50
+ </div>
51
+
52
+ <div class="modal-footer">
53
+ </div>
54
+ </div>
@@ -0,0 +1,39 @@
1
+ {% extends 'generic/object.html' %}
2
+ {% load static %}
3
+ {% load helpers %}
4
+ {% load plugins %}
5
+ {% load render_table from django_tables2 %}
6
+ {% load i18n %}
7
+
8
+ {% block content %}
9
+ <div class="row mb-3">
10
+ <div class="col col-md-6">
11
+ <div class="card">
12
+ <h5 class="card-header">{% trans "Endpoint Information" %}</h5>
13
+ <div class="card-body">
14
+ <table class="table table-hover attr-table">
15
+ <tr>
16
+ <th scope="row">{% trans "Name" %}</th>
17
+ <td>{{ object.name }}</td>
18
+ </tr>
19
+ <tr>
20
+ <th scope="row">{% trans "Description" %}</th>
21
+ <td>{{ object.description | placeholder }}</td>
22
+ </tr>
23
+ <tr>
24
+ <th scope="row">{% trans "Endpoint" %}</th>
25
+ <td>{{ object.endpoint | placeholder }}</td>
26
+ </tr>
27
+ </table>
28
+ </div>
29
+ </div>
30
+ {% plugin_left_page object %}
31
+ </div>
32
+ <div class="col col-12 col-md-6">
33
+ {% include 'inc/panels/related_objects.html' %}
34
+ {% include 'inc/panels/comments.html' %}
35
+ {% include 'inc/panels/custom_fields.html' %}
36
+ {% plugin_right_page object %}
37
+ </div>
38
+ </div>
39
+ {% endblock %}
@@ -0,0 +1,51 @@
1
+ {% extends 'generic/object.html' %}
2
+ {% load static %}
3
+ {% load helpers %}
4
+ {% load plugins %}
5
+ {% load render_table from django_tables2 %}
6
+ {% load i18n %}
7
+
8
+ {% block extra_controls %}
9
+ <a href="#"
10
+ hx-get="{% url 'plugins:ipfabric_netbox:ipfabricfilter_combined_expressions' pk=object.pk %}"
11
+ hx-target="#htmx-modal-content"
12
+ data-bs-toggle="modal"
13
+ data-bs-target="#htmx-modal"
14
+ class="btn btn-primary"
15
+ >
16
+ <i class="mdi mdi-filter-variant"></i> {% trans "Combined Expressions" %}
17
+ </a>
18
+ {% endblock %}
19
+
20
+ {% block content %}
21
+ <div class="row mb-3">
22
+ <div class="col col-md-6">
23
+ <div class="card">
24
+ <h5 class="card-header">{% trans "Filter Information" %}</h5>
25
+ <div class="card-body">
26
+ <table class="table table-hover attr-table">
27
+ <tr>
28
+ <th scope="row">{% trans "Name" %}</th>
29
+ <td>{{ object.name }}</td>
30
+ </tr>
31
+ <tr>
32
+ <th scope="row">{% trans "Description" %}</th>
33
+ <td>{{ object.description | placeholder }}</td>
34
+ </tr>
35
+ <tr>
36
+ <th scope="row">{% trans "Filter Type" %}</th>
37
+ <td>{{ object.filter_type.upper | placeholder }}</td>
38
+ </tr>
39
+ </table>
40
+ </div>
41
+ </div>
42
+ {% plugin_left_page object %}
43
+ </div>
44
+ <div class="col col-12 col-md-6">
45
+ {% include 'inc/panels/related_objects.html' %}
46
+ {% include 'inc/panels/comments.html' %}
47
+ {% include 'inc/panels/custom_fields.html' %}
48
+ {% plugin_right_page object %}
49
+ </div>
50
+ </div>
51
+ {% endblock %}