netbox-cisco-ise 0.1.4__py3-none-any.whl → 0.1.7__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_cisco_ise/__init__.py +78 -1
- netbox_cisco_ise/ise_client.py +3 -11
- netbox_cisco_ise/templates/netbox_cisco_ise/device_tab.html +24 -0
- netbox_cisco_ise/templates/netbox_cisco_ise/endpoint_tab_content.html +216 -0
- netbox_cisco_ise/templates/netbox_cisco_ise/nad_tab_content.html +193 -0
- netbox_cisco_ise/templates/netbox_cisco_ise/netbox_endpoint_tab.html +24 -0
- netbox_cisco_ise/urls.py +10 -1
- netbox_cisco_ise/views.py +189 -57
- {netbox_cisco_ise-0.1.4.dist-info → netbox_cisco_ise-0.1.7.dist-info}/METADATA +7 -1
- netbox_cisco_ise-0.1.7.dist-info/RECORD +17 -0
- netbox_cisco_ise-0.1.4.dist-info/RECORD +0 -13
- {netbox_cisco_ise-0.1.4.dist-info → netbox_cisco_ise-0.1.7.dist-info}/WHEEL +0 -0
- {netbox_cisco_ise-0.1.4.dist-info → netbox_cisco_ise-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {netbox_cisco_ise-0.1.4.dist-info → netbox_cisco_ise-0.1.7.dist-info}/top_level.txt +0 -0
netbox_cisco_ise/__init__.py
CHANGED
|
@@ -5,9 +5,13 @@ Display Cisco Identity Services Engine (ISE) endpoint and NAD information in Dev
|
|
|
5
5
|
Shows endpoint identity, profiling data, active session status, and network access device details.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import logging
|
|
9
|
+
|
|
8
10
|
from netbox.plugins import PluginConfig
|
|
9
11
|
|
|
10
|
-
__version__ = "0.1.
|
|
12
|
+
__version__ = "0.1.7"
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class CiscoISEConfig(PluginConfig):
|
|
@@ -54,7 +58,80 @@ class CiscoISEConfig(PluginConfig):
|
|
|
54
58
|
"lookup": "nad",
|
|
55
59
|
}, # Default: Cisco devices as NADs
|
|
56
60
|
],
|
|
61
|
+
# Endpoint mappings (requires netbox-endpoints plugin)
|
|
62
|
+
# Format: list of dicts with manufacturer (regex), endpoint_type (regex, optional)
|
|
63
|
+
# All endpoints use MAC lookup since they're endpoint devices
|
|
64
|
+
#
|
|
65
|
+
# Example:
|
|
66
|
+
# "endpoint_mappings": [
|
|
67
|
+
# {"manufacturer": "vocera"}, # All Vocera endpoints
|
|
68
|
+
# {"manufacturer": "cisco", "endpoint_type": ".*phone.*"}, # Cisco phones
|
|
69
|
+
# ]
|
|
70
|
+
# If empty, shows tab for ALL endpoints with a MAC address
|
|
71
|
+
"endpoint_mappings": [],
|
|
57
72
|
}
|
|
58
73
|
|
|
74
|
+
def ready(self):
|
|
75
|
+
"""Register endpoint view if netbox_endpoints is available."""
|
|
76
|
+
super().ready()
|
|
77
|
+
self._register_endpoint_views()
|
|
78
|
+
|
|
79
|
+
def _register_endpoint_views(self):
|
|
80
|
+
"""Register Cisco ISE tab for Endpoints if plugin is installed."""
|
|
81
|
+
import sys
|
|
82
|
+
|
|
83
|
+
# Quick check if netbox_endpoints is available
|
|
84
|
+
if "netbox_endpoints" not in sys.modules:
|
|
85
|
+
try:
|
|
86
|
+
import importlib.util
|
|
87
|
+
|
|
88
|
+
if importlib.util.find_spec("netbox_endpoints") is None:
|
|
89
|
+
logger.debug("netbox_endpoints not installed, skipping endpoint view registration")
|
|
90
|
+
return
|
|
91
|
+
except Exception:
|
|
92
|
+
logger.debug("netbox_endpoints not available, skipping endpoint view registration")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
from django.shortcuts import render
|
|
97
|
+
from netbox.views import generic
|
|
98
|
+
from netbox_endpoints.models import Endpoint
|
|
99
|
+
from utilities.views import ViewTab, register_model_view
|
|
100
|
+
|
|
101
|
+
from .views import should_show_ise_tab_endpoint
|
|
102
|
+
|
|
103
|
+
@register_model_view(Endpoint, name="cisco_ise", path="cisco-ise")
|
|
104
|
+
class EndpointISEView(generic.ObjectView):
|
|
105
|
+
"""Display Cisco ISE endpoint details for a netbox Endpoint."""
|
|
106
|
+
|
|
107
|
+
queryset = Endpoint.objects.all()
|
|
108
|
+
template_name = "netbox_cisco_ise/netbox_endpoint_tab.html"
|
|
109
|
+
|
|
110
|
+
tab = ViewTab(
|
|
111
|
+
label="Cisco ISE",
|
|
112
|
+
weight=9001,
|
|
113
|
+
permission="netbox_endpoints.view_endpoint",
|
|
114
|
+
hide_if_empty=False,
|
|
115
|
+
visible=should_show_ise_tab_endpoint,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get(self, request, pk):
|
|
119
|
+
endpoint = Endpoint.objects.get(pk=pk)
|
|
120
|
+
return render(
|
|
121
|
+
request,
|
|
122
|
+
self.template_name,
|
|
123
|
+
{
|
|
124
|
+
"object": endpoint,
|
|
125
|
+
"tab": self.tab,
|
|
126
|
+
"loading": True,
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
logger.info("Registered Cisco ISE tab for Endpoint model")
|
|
131
|
+
except ImportError:
|
|
132
|
+
logger.debug("netbox_endpoints not installed, skipping endpoint view registration")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.warning(f"Could not register endpoint views: {e}")
|
|
135
|
+
|
|
59
136
|
|
|
60
137
|
config = CiscoISEConfig
|
netbox_cisco_ise/ise_client.py
CHANGED
|
@@ -275,9 +275,7 @@ class ISEClient:
|
|
|
275
275
|
cached["cached"] = True
|
|
276
276
|
return cached
|
|
277
277
|
|
|
278
|
-
result = self._make_ers_request(
|
|
279
|
-
"/networkdevice", params={"filter": f"ipaddress.EQ.{ip_address}"}
|
|
280
|
-
)
|
|
278
|
+
result = self._make_ers_request("/networkdevice", params={"filter": f"ipaddress.EQ.{ip_address}"})
|
|
281
279
|
|
|
282
280
|
return self._process_nad_result(result, cache_key)
|
|
283
281
|
|
|
@@ -297,9 +295,7 @@ class ISEClient:
|
|
|
297
295
|
cached["cached"] = True
|
|
298
296
|
return cached
|
|
299
297
|
|
|
300
|
-
result = self._make_ers_request(
|
|
301
|
-
"/networkdevice", params={"filter": f"name.CONTAINS.{name}"}
|
|
302
|
-
)
|
|
298
|
+
result = self._make_ers_request("/networkdevice", params={"filter": f"name.CONTAINS.{name}"})
|
|
303
299
|
|
|
304
300
|
return self._process_nad_result(result, cache_key)
|
|
305
301
|
|
|
@@ -365,11 +361,7 @@ class ISEClient:
|
|
|
365
361
|
"ro_community": bool(snmp_settings.get("roCommunity")),
|
|
366
362
|
"polling_interval": snmp_settings.get("pollingInterval"),
|
|
367
363
|
},
|
|
368
|
-
"trustsec_enabled": bool(
|
|
369
|
-
trustsec_settings.get("deviceAuthenticationSettings", {}).get(
|
|
370
|
-
"sgaDeviceId"
|
|
371
|
-
)
|
|
372
|
-
),
|
|
364
|
+
"trustsec_enabled": bool(trustsec_settings.get("deviceAuthenticationSettings", {}).get("sgaDeviceId")),
|
|
373
365
|
"coA_port": nad.get("coaPort"),
|
|
374
366
|
"cached": False,
|
|
375
367
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% extends 'dcim/device/base.html' %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
{% if loading %}
|
|
6
|
+
<div id="ise-content"
|
|
7
|
+
hx-get="{% url 'plugins:netbox_cisco_ise:device_content' pk=object.pk %}"
|
|
8
|
+
hx-trigger="load"
|
|
9
|
+
hx-swap="innerHTML">
|
|
10
|
+
<div class="d-flex justify-content-center align-items-center py-5">
|
|
11
|
+
<div class="text-center">
|
|
12
|
+
<div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
|
|
13
|
+
<span class="visually-hidden">Loading...</span>
|
|
14
|
+
</div>
|
|
15
|
+
<p class="text-muted mb-0">Loading Cisco ISE data...</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
{% elif error %}
|
|
20
|
+
<div class="alert alert-warning">
|
|
21
|
+
<i class="mdi mdi-alert"></i> {{ error }}
|
|
22
|
+
</div>
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endblock %}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
{% load helpers %}
|
|
2
|
+
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="col-12">
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-warning">
|
|
7
|
+
<i class="mdi mdi-alert"></i> {{ error }}
|
|
8
|
+
</div>
|
|
9
|
+
{% elif ise_data %}
|
|
10
|
+
<div class="row">
|
|
11
|
+
<!-- Session Status Card -->
|
|
12
|
+
<div class="col-md-4 mb-3">
|
|
13
|
+
<div class="card h-100">
|
|
14
|
+
<div class="card-header">
|
|
15
|
+
<h5 class="card-title mb-0">
|
|
16
|
+
<i class="mdi mdi-lan-connect"></i> Session Status
|
|
17
|
+
</h5>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="card-body text-center">
|
|
20
|
+
{% if session_data.connected %}
|
|
21
|
+
<div class="p-3 mb-2 bg-success rounded">
|
|
22
|
+
<span class="text-white fs-3 fw-bold">
|
|
23
|
+
<i class="mdi mdi-check-circle"></i> Connected
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
{% if session_data.framed_ip_address %}
|
|
27
|
+
<div class="mt-2">
|
|
28
|
+
<span class="text-muted">IP Address:</span>
|
|
29
|
+
<code class="ms-2">{{ session_data.framed_ip_address }}</code>
|
|
30
|
+
</div>
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% if session_data.authorization_profile %}
|
|
33
|
+
<div class="mt-1">
|
|
34
|
+
<span class="text-muted">Authorization:</span>
|
|
35
|
+
<span class="badge bg-info text-dark ms-2">{{ session_data.authorization_profile }}</span>
|
|
36
|
+
</div>
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% if session_data.acct_session_time %}
|
|
39
|
+
<div class="mt-1 text-muted small">
|
|
40
|
+
Session time: {{ session_data.acct_session_time }} seconds
|
|
41
|
+
</div>
|
|
42
|
+
{% endif %}
|
|
43
|
+
{% else %}
|
|
44
|
+
<div class="p-3 mb-2 bg-secondary rounded">
|
|
45
|
+
<span class="text-white fs-3 fw-bold">
|
|
46
|
+
<i class="mdi mdi-lan-disconnect"></i> Disconnected
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="mt-2 text-muted">
|
|
50
|
+
No active session found
|
|
51
|
+
</div>
|
|
52
|
+
{% endif %}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Endpoint Identity Card -->
|
|
58
|
+
<div class="col-md-4 mb-3">
|
|
59
|
+
<div class="card h-100">
|
|
60
|
+
<div class="card-header">
|
|
61
|
+
<h5 class="card-title mb-0">
|
|
62
|
+
<i class="mdi mdi-account-badge"></i> Endpoint Identity
|
|
63
|
+
</h5>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="card-body">
|
|
66
|
+
<table class="table table-sm table-borderless mb-0">
|
|
67
|
+
<tr>
|
|
68
|
+
<th class="text-muted" style="width: 40%">MAC Address:</th>
|
|
69
|
+
<td><code>{{ ise_data.mac_address }}</code></td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<th class="text-muted">Profile:</th>
|
|
73
|
+
<td>{{ ise_data.profile_name|default:"N/A" }}</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr>
|
|
76
|
+
<th class="text-muted">Identity Group:</th>
|
|
77
|
+
<td>{{ ise_data.group_name|default:"N/A" }}</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<th class="text-muted">Static Assignment:</th>
|
|
81
|
+
<td>
|
|
82
|
+
{% if ise_data.static_group_assignment %}
|
|
83
|
+
<span class="badge bg-success text-white">Yes</span>
|
|
84
|
+
{% else %}
|
|
85
|
+
<span class="badge bg-secondary text-white">No</span>
|
|
86
|
+
{% endif %}
|
|
87
|
+
</td>
|
|
88
|
+
</tr>
|
|
89
|
+
{% if ise_data.portal_user %}
|
|
90
|
+
<tr>
|
|
91
|
+
<th class="text-muted">Portal User:</th>
|
|
92
|
+
<td>{{ ise_data.portal_user }}</td>
|
|
93
|
+
</tr>
|
|
94
|
+
{% endif %}
|
|
95
|
+
</table>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- Connection Details Card -->
|
|
101
|
+
<div class="col-md-4 mb-3">
|
|
102
|
+
<div class="card h-100">
|
|
103
|
+
<div class="card-header">
|
|
104
|
+
<h5 class="card-title mb-0">
|
|
105
|
+
<i class="mdi mdi-router-wireless"></i> Connection Details
|
|
106
|
+
</h5>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="card-body">
|
|
109
|
+
{% if session_data.connected %}
|
|
110
|
+
<table class="table table-sm table-borderless mb-0">
|
|
111
|
+
{% if session_data.nas_ip_address %}
|
|
112
|
+
<tr>
|
|
113
|
+
<th class="text-muted" style="width: 40%">Connected To:</th>
|
|
114
|
+
<td><code>{{ session_data.nas_ip_address }}</code></td>
|
|
115
|
+
</tr>
|
|
116
|
+
{% endif %}
|
|
117
|
+
{% if session_data.nas_port_id %}
|
|
118
|
+
<tr>
|
|
119
|
+
<th class="text-muted">Port:</th>
|
|
120
|
+
<td>{{ session_data.nas_port_id }}</td>
|
|
121
|
+
</tr>
|
|
122
|
+
{% endif %}
|
|
123
|
+
{% if session_data.vlan %}
|
|
124
|
+
<tr>
|
|
125
|
+
<th class="text-muted">VLAN:</th>
|
|
126
|
+
<td>{{ session_data.vlan }}</td>
|
|
127
|
+
</tr>
|
|
128
|
+
{% endif %}
|
|
129
|
+
{% if session_data.ssid %}
|
|
130
|
+
<tr>
|
|
131
|
+
<th class="text-muted">SSID:</th>
|
|
132
|
+
<td>{{ session_data.ssid }}</td>
|
|
133
|
+
</tr>
|
|
134
|
+
{% endif %}
|
|
135
|
+
{% if session_data.auth_method %}
|
|
136
|
+
<tr>
|
|
137
|
+
<th class="text-muted">Auth Method:</th>
|
|
138
|
+
<td>{{ session_data.auth_method }}</td>
|
|
139
|
+
</tr>
|
|
140
|
+
{% endif %}
|
|
141
|
+
{% if session_data.security_group %}
|
|
142
|
+
<tr>
|
|
143
|
+
<th class="text-muted">Security Group:</th>
|
|
144
|
+
<td><span class="badge bg-primary text-white">{{ session_data.security_group }}</span></td>
|
|
145
|
+
</tr>
|
|
146
|
+
{% endif %}
|
|
147
|
+
</table>
|
|
148
|
+
{% else %}
|
|
149
|
+
<div class="text-center text-muted py-4">
|
|
150
|
+
<i class="mdi mdi-lan-disconnect mdi-48px"></i>
|
|
151
|
+
<p class="mt-2">No active connection</p>
|
|
152
|
+
</div>
|
|
153
|
+
{% endif %}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Custom Attributes -->
|
|
160
|
+
{% if ise_data.custom_attributes %}
|
|
161
|
+
<div class="row">
|
|
162
|
+
<div class="col-12 mb-3">
|
|
163
|
+
<div class="card">
|
|
164
|
+
<div class="card-header">
|
|
165
|
+
<h5 class="card-title mb-0">
|
|
166
|
+
<i class="mdi mdi-tag-multiple"></i> Custom Attributes
|
|
167
|
+
</h5>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="card-body">
|
|
170
|
+
<table class="table table-sm table-hover">
|
|
171
|
+
<thead>
|
|
172
|
+
<tr>
|
|
173
|
+
<th>Attribute</th>
|
|
174
|
+
<th>Value</th>
|
|
175
|
+
</tr>
|
|
176
|
+
</thead>
|
|
177
|
+
<tbody>
|
|
178
|
+
{% for key, value in ise_data.custom_attributes.items %}
|
|
179
|
+
<tr>
|
|
180
|
+
<td>{{ key }}</td>
|
|
181
|
+
<td>{{ value }}</td>
|
|
182
|
+
</tr>
|
|
183
|
+
{% endfor %}
|
|
184
|
+
</tbody>
|
|
185
|
+
</table>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
{% endif %}
|
|
191
|
+
|
|
192
|
+
<!-- Cache indicator and external link -->
|
|
193
|
+
<div class="mt-3 d-flex justify-content-between align-items-center">
|
|
194
|
+
<div>
|
|
195
|
+
{% if ise_data.cached %}
|
|
196
|
+
<span class="text-muted small">
|
|
197
|
+
<i class="mdi mdi-cached"></i> Data from cache
|
|
198
|
+
</span>
|
|
199
|
+
{% endif %}
|
|
200
|
+
</div>
|
|
201
|
+
<div>
|
|
202
|
+
{% if ise_url %}
|
|
203
|
+
<a href="{{ ise_url }}" target="_blank" class="btn btn-outline-primary btn-sm">
|
|
204
|
+
<i class="mdi mdi-open-in-new"></i> Open Cisco ISE
|
|
205
|
+
</a>
|
|
206
|
+
{% endif %}
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{% else %}
|
|
211
|
+
<div class="alert alert-info">
|
|
212
|
+
<i class="mdi mdi-information"></i> No endpoint data available from ISE.
|
|
213
|
+
</div>
|
|
214
|
+
{% endif %}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
{% load helpers %}
|
|
2
|
+
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="col-12">
|
|
5
|
+
{% if error %}
|
|
6
|
+
<div class="alert alert-warning">
|
|
7
|
+
<i class="mdi mdi-alert"></i> {{ error }}
|
|
8
|
+
</div>
|
|
9
|
+
{% elif ise_data %}
|
|
10
|
+
<div class="row">
|
|
11
|
+
<!-- NAD Registration Status Card -->
|
|
12
|
+
<div class="col-md-4 mb-3">
|
|
13
|
+
<div class="card h-100">
|
|
14
|
+
<div class="card-header">
|
|
15
|
+
<h5 class="card-title mb-0">
|
|
16
|
+
<i class="mdi mdi-check-decagram"></i> ISE Registration
|
|
17
|
+
</h5>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="card-body text-center">
|
|
20
|
+
<div class="p-3 mb-2 bg-success rounded">
|
|
21
|
+
<span class="text-white fs-3 fw-bold">
|
|
22
|
+
<i class="mdi mdi-check-circle"></i> Registered
|
|
23
|
+
</span>
|
|
24
|
+
</div>
|
|
25
|
+
{% if ise_data.name %}
|
|
26
|
+
<div class="mt-2">
|
|
27
|
+
<span class="text-muted">NAD Name:</span>
|
|
28
|
+
<strong class="ms-2">{{ ise_data.name }}</strong>
|
|
29
|
+
</div>
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if ise_data.profile_name %}
|
|
32
|
+
<div class="mt-1">
|
|
33
|
+
<span class="text-muted">Profile:</span>
|
|
34
|
+
<span class="badge bg-info text-dark ms-2">{{ ise_data.profile_name }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
{% endif %}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- Device Details Card -->
|
|
42
|
+
<div class="col-md-4 mb-3">
|
|
43
|
+
<div class="card h-100">
|
|
44
|
+
<div class="card-header">
|
|
45
|
+
<h5 class="card-title mb-0">
|
|
46
|
+
<i class="mdi mdi-router"></i> Device Details
|
|
47
|
+
</h5>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="card-body">
|
|
50
|
+
<table class="table table-sm table-borderless mb-0">
|
|
51
|
+
{% if ise_data.ip_addresses %}
|
|
52
|
+
<tr>
|
|
53
|
+
<th class="text-muted" style="width: 40%">IP Address(es):</th>
|
|
54
|
+
<td>
|
|
55
|
+
{% for ip in ise_data.ip_addresses %}
|
|
56
|
+
<code>{{ ip }}</code>{% if not forloop.last %}, {% endif %}
|
|
57
|
+
{% endfor %}
|
|
58
|
+
</td>
|
|
59
|
+
</tr>
|
|
60
|
+
{% endif %}
|
|
61
|
+
{% if ise_data.model_name %}
|
|
62
|
+
<tr>
|
|
63
|
+
<th class="text-muted">Model:</th>
|
|
64
|
+
<td>{{ ise_data.model_name }}</td>
|
|
65
|
+
</tr>
|
|
66
|
+
{% endif %}
|
|
67
|
+
{% if ise_data.software_version %}
|
|
68
|
+
<tr>
|
|
69
|
+
<th class="text-muted">Software:</th>
|
|
70
|
+
<td>{{ ise_data.software_version }}</td>
|
|
71
|
+
</tr>
|
|
72
|
+
{% endif %}
|
|
73
|
+
{% if ise_data.description %}
|
|
74
|
+
<tr>
|
|
75
|
+
<th class="text-muted">Description:</th>
|
|
76
|
+
<td>{{ ise_data.description }}</td>
|
|
77
|
+
</tr>
|
|
78
|
+
{% endif %}
|
|
79
|
+
{% if ise_data.coA_port %}
|
|
80
|
+
<tr>
|
|
81
|
+
<th class="text-muted">CoA Port:</th>
|
|
82
|
+
<td>{{ ise_data.coA_port }}</td>
|
|
83
|
+
</tr>
|
|
84
|
+
{% endif %}
|
|
85
|
+
</table>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- Authentication Settings Card -->
|
|
91
|
+
<div class="col-md-4 mb-3">
|
|
92
|
+
<div class="card h-100">
|
|
93
|
+
<div class="card-header">
|
|
94
|
+
<h5 class="card-title mb-0">
|
|
95
|
+
<i class="mdi mdi-shield-key"></i> Authentication Settings
|
|
96
|
+
</h5>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="card-body">
|
|
99
|
+
<table class="table table-sm table-borderless mb-0">
|
|
100
|
+
<tr>
|
|
101
|
+
<th class="text-muted" style="width: 50%">RADIUS:</th>
|
|
102
|
+
<td>
|
|
103
|
+
{% if ise_data.authentication_settings.radius_enabled %}
|
|
104
|
+
<span class="badge bg-success text-white">Enabled</span>
|
|
105
|
+
{% else %}
|
|
106
|
+
<span class="badge bg-warning text-dark">Disabled</span>
|
|
107
|
+
{% endif %}
|
|
108
|
+
</td>
|
|
109
|
+
</tr>
|
|
110
|
+
<tr>
|
|
111
|
+
<th class="text-muted">TACACS+:</th>
|
|
112
|
+
<td>
|
|
113
|
+
{% if ise_data.tacacs_settings.enabled %}
|
|
114
|
+
<span class="badge bg-success text-white">Enabled</span>
|
|
115
|
+
{% else %}
|
|
116
|
+
<span class="badge bg-warning text-dark">Disabled</span>
|
|
117
|
+
{% endif %}
|
|
118
|
+
</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<th class="text-muted">TrustSec:</th>
|
|
122
|
+
<td>
|
|
123
|
+
{% if ise_data.trustsec_enabled %}
|
|
124
|
+
<span class="badge bg-success text-white">Enabled</span>
|
|
125
|
+
{% else %}
|
|
126
|
+
<span class="badge bg-warning text-dark">Disabled</span>
|
|
127
|
+
{% endif %}
|
|
128
|
+
</td>
|
|
129
|
+
</tr>
|
|
130
|
+
{% if ise_data.snmp_settings.version %}
|
|
131
|
+
<tr>
|
|
132
|
+
<th class="text-muted">SNMP:</th>
|
|
133
|
+
<td>{{ ise_data.snmp_settings.version }}</td>
|
|
134
|
+
</tr>
|
|
135
|
+
{% endif %}
|
|
136
|
+
</table>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Network Device Groups -->
|
|
143
|
+
{% if ise_data.groups %}
|
|
144
|
+
<div class="row">
|
|
145
|
+
<div class="col-12 mb-3">
|
|
146
|
+
<div class="card">
|
|
147
|
+
<div class="card-header">
|
|
148
|
+
<h5 class="card-title mb-0">
|
|
149
|
+
<i class="mdi mdi-folder-network"></i> Network Device Groups
|
|
150
|
+
</h5>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="card-body">
|
|
153
|
+
<div class="row">
|
|
154
|
+
{% for category, value in ise_data.groups.items %}
|
|
155
|
+
<div class="col-md-3 col-sm-6 mb-2">
|
|
156
|
+
<div class="border rounded p-2">
|
|
157
|
+
<div class="text-muted small">{{ category }}</div>
|
|
158
|
+
<div class="fw-bold">{{ value }}</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
{% endfor %}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
{% endif %}
|
|
168
|
+
|
|
169
|
+
<!-- Cache indicator and external link -->
|
|
170
|
+
<div class="mt-3 d-flex justify-content-between align-items-center">
|
|
171
|
+
<div>
|
|
172
|
+
{% if ise_data.cached %}
|
|
173
|
+
<span class="text-muted small">
|
|
174
|
+
<i class="mdi mdi-cached"></i> Data from cache
|
|
175
|
+
</span>
|
|
176
|
+
{% endif %}
|
|
177
|
+
</div>
|
|
178
|
+
<div>
|
|
179
|
+
{% if ise_url %}
|
|
180
|
+
<a href="{{ ise_url }}" target="_blank" class="btn btn-outline-primary btn-sm">
|
|
181
|
+
<i class="mdi mdi-open-in-new"></i> Open Cisco ISE
|
|
182
|
+
</a>
|
|
183
|
+
{% endif %}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
{% else %}
|
|
188
|
+
<div class="alert alert-info">
|
|
189
|
+
<i class="mdi mdi-information"></i> This device is not registered as a Network Access Device in ISE.
|
|
190
|
+
</div>
|
|
191
|
+
{% endif %}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% extends 'netbox_endpoints/endpoint.html' %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
{% if loading %}
|
|
6
|
+
<div id="ise-content"
|
|
7
|
+
hx-get="{% url 'plugins:netbox_cisco_ise:endpoint_content' pk=object.pk %}"
|
|
8
|
+
hx-trigger="load"
|
|
9
|
+
hx-swap="innerHTML">
|
|
10
|
+
<div class="d-flex justify-content-center align-items-center py-5">
|
|
11
|
+
<div class="text-center">
|
|
12
|
+
<div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
|
|
13
|
+
<span class="visually-hidden">Loading...</span>
|
|
14
|
+
</div>
|
|
15
|
+
<p class="text-muted mb-0">Loading Cisco ISE data...</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
{% elif error %}
|
|
20
|
+
<div class="alert alert-warning">
|
|
21
|
+
<i class="mdi mdi-alert"></i> {{ error }}
|
|
22
|
+
</div>
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endblock %}
|
netbox_cisco_ise/urls.py
CHANGED
|
@@ -4,9 +4,18 @@ URL routing for NetBox Cisco ISE Plugin
|
|
|
4
4
|
|
|
5
5
|
from django.urls import path
|
|
6
6
|
|
|
7
|
-
from .views import ISESettingsView, TestConnectionView
|
|
7
|
+
from .views import ENDPOINTS_PLUGIN_INSTALLED, DeviceISEContentView, ISESettingsView, TestConnectionView
|
|
8
8
|
|
|
9
9
|
urlpatterns = [
|
|
10
10
|
path("settings/", ISESettingsView.as_view(), name="settings"),
|
|
11
11
|
path("test-connection/", TestConnectionView.as_view(), name="test_connection"),
|
|
12
|
+
path("device/<int:pk>/content/", DeviceISEContentView.as_view(), name="device_content"),
|
|
12
13
|
]
|
|
14
|
+
|
|
15
|
+
# Add endpoint URLs if netbox_endpoints is installed
|
|
16
|
+
if ENDPOINTS_PLUGIN_INSTALLED:
|
|
17
|
+
from .views import EndpointISEContentView
|
|
18
|
+
|
|
19
|
+
urlpatterns.append(
|
|
20
|
+
path("endpoint/<int:pk>/content/", EndpointISEContentView.as_view(), name="endpoint_content"),
|
|
21
|
+
)
|
netbox_cisco_ise/views.py
CHANGED
|
@@ -9,14 +9,24 @@ import re
|
|
|
9
9
|
|
|
10
10
|
from dcim.models import Device
|
|
11
11
|
from django.conf import settings
|
|
12
|
-
from django.
|
|
12
|
+
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
|
13
|
+
from django.http import HttpResponse, JsonResponse
|
|
13
14
|
from django.shortcuts import render
|
|
15
|
+
from django.template.loader import render_to_string
|
|
14
16
|
from django.views import View
|
|
15
17
|
from netbox.views import generic
|
|
16
18
|
from utilities.views import ViewTab, register_model_view
|
|
17
19
|
|
|
18
20
|
from .ise_client import get_client
|
|
19
21
|
|
|
22
|
+
# Check if netbox_endpoints plugin is installed
|
|
23
|
+
try:
|
|
24
|
+
from netbox_endpoints.models import Endpoint
|
|
25
|
+
|
|
26
|
+
ENDPOINTS_PLUGIN_INSTALLED = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
ENDPOINTS_PLUGIN_INSTALLED = False
|
|
29
|
+
|
|
20
30
|
|
|
21
31
|
def is_valid_mac(value):
|
|
22
32
|
"""Check if a value looks like a MAC address."""
|
|
@@ -51,18 +61,10 @@ def get_device_lookup_method(device):
|
|
|
51
61
|
|
|
52
62
|
# Get device info for matching
|
|
53
63
|
manufacturer = device.device_type.manufacturer
|
|
54
|
-
manufacturer_slug = (
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
manufacturer.name.lower() if manufacturer and manufacturer.name else ""
|
|
59
|
-
)
|
|
60
|
-
device_type_slug = (
|
|
61
|
-
device.device_type.slug.lower() if device.device_type.slug else ""
|
|
62
|
-
)
|
|
63
|
-
device_type_model = (
|
|
64
|
-
device.device_type.model.lower() if device.device_type.model else ""
|
|
65
|
-
)
|
|
64
|
+
manufacturer_slug = manufacturer.slug.lower() if manufacturer and manufacturer.slug else ""
|
|
65
|
+
manufacturer_name = manufacturer.name.lower() if manufacturer and manufacturer.name else ""
|
|
66
|
+
device_type_slug = device.device_type.slug.lower() if device.device_type.slug else ""
|
|
67
|
+
device_type_model = device.device_type.model.lower() if device.device_type.model else ""
|
|
66
68
|
|
|
67
69
|
# Check each mapping
|
|
68
70
|
for mapping in mappings:
|
|
@@ -74,15 +76,12 @@ def get_device_lookup_method(device):
|
|
|
74
76
|
manufacturer_match = False
|
|
75
77
|
if manufacturer_pattern:
|
|
76
78
|
try:
|
|
77
|
-
if re.search(
|
|
78
|
-
manufacturer_pattern,
|
|
79
|
-
)
|
|
79
|
+
if re.search(manufacturer_pattern, manufacturer_slug, re.IGNORECASE) or re.search(
|
|
80
|
+
manufacturer_pattern, manufacturer_name, re.IGNORECASE
|
|
81
|
+
):
|
|
80
82
|
manufacturer_match = True
|
|
81
83
|
except re.error:
|
|
82
|
-
if
|
|
83
|
-
manufacturer_pattern in manufacturer_slug
|
|
84
|
-
or manufacturer_pattern in manufacturer_name
|
|
85
|
-
):
|
|
84
|
+
if manufacturer_pattern in manufacturer_slug or manufacturer_pattern in manufacturer_name:
|
|
86
85
|
manufacturer_match = True
|
|
87
86
|
|
|
88
87
|
if not manufacturer_match:
|
|
@@ -92,15 +91,12 @@ def get_device_lookup_method(device):
|
|
|
92
91
|
if device_type_pattern:
|
|
93
92
|
device_type_match = False
|
|
94
93
|
try:
|
|
95
|
-
if re.search(
|
|
96
|
-
device_type_pattern,
|
|
97
|
-
)
|
|
94
|
+
if re.search(device_type_pattern, device_type_slug, re.IGNORECASE) or re.search(
|
|
95
|
+
device_type_pattern, device_type_model, re.IGNORECASE
|
|
96
|
+
):
|
|
98
97
|
device_type_match = True
|
|
99
98
|
except re.error:
|
|
100
|
-
if
|
|
101
|
-
device_type_pattern in device_type_slug
|
|
102
|
-
or device_type_pattern in device_type_model
|
|
103
|
-
):
|
|
99
|
+
if device_type_pattern in device_type_slug or device_type_pattern in device_type_model:
|
|
104
100
|
device_type_match = True
|
|
105
101
|
|
|
106
102
|
if not device_type_match:
|
|
@@ -133,10 +129,10 @@ def should_show_ise_tab(device):
|
|
|
133
129
|
|
|
134
130
|
@register_model_view(Device, name="cisco_ise", path="cisco-ise")
|
|
135
131
|
class DeviceISEView(generic.ObjectView):
|
|
136
|
-
"""Display Cisco ISE endpoint/NAD details for a Device."""
|
|
132
|
+
"""Display Cisco ISE endpoint/NAD details for a Device with async loading."""
|
|
137
133
|
|
|
138
134
|
queryset = Device.objects.all()
|
|
139
|
-
template_name = "netbox_cisco_ise/
|
|
135
|
+
template_name = "netbox_cisco_ise/device_tab.html"
|
|
140
136
|
|
|
141
137
|
tab = ViewTab(
|
|
142
138
|
label="Cisco ISE",
|
|
@@ -147,13 +143,28 @@ class DeviceISEView(generic.ObjectView):
|
|
|
147
143
|
)
|
|
148
144
|
|
|
149
145
|
def get(self, request, pk):
|
|
150
|
-
"""
|
|
151
|
-
device = (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
146
|
+
"""Render initial tab with loading spinner - content loads via htmx."""
|
|
147
|
+
device = Device.objects.get(pk=pk)
|
|
148
|
+
return render(
|
|
149
|
+
request,
|
|
150
|
+
self.template_name,
|
|
151
|
+
{
|
|
152
|
+
"object": device,
|
|
153
|
+
"tab": self.tab,
|
|
154
|
+
"loading": True,
|
|
155
|
+
},
|
|
155
156
|
)
|
|
156
157
|
|
|
158
|
+
|
|
159
|
+
class DeviceISEContentView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
160
|
+
"""HTMX endpoint that returns ISE content for async loading."""
|
|
161
|
+
|
|
162
|
+
permission_required = "dcim.view_device"
|
|
163
|
+
|
|
164
|
+
def get(self, request, pk):
|
|
165
|
+
"""Fetch ISE data and return HTML content."""
|
|
166
|
+
device = Device.objects.select_related("device_type__manufacturer").prefetch_related("interfaces").get(pk=pk)
|
|
167
|
+
|
|
157
168
|
client = get_client()
|
|
158
169
|
config = settings.PLUGINS_CONFIG.get("netbox_cisco_ise", {})
|
|
159
170
|
|
|
@@ -174,10 +185,7 @@ class DeviceISEView(generic.ObjectView):
|
|
|
174
185
|
if "error" not in ise_data:
|
|
175
186
|
# Also get session data for connected endpoints
|
|
176
187
|
session_data = client.get_active_session_by_mac(mac_address)
|
|
177
|
-
if (
|
|
178
|
-
"error" in session_data
|
|
179
|
-
and session_data.get("connected") is False
|
|
180
|
-
):
|
|
188
|
+
if "error" in session_data and session_data.get("connected") is False:
|
|
181
189
|
# Not connected is fine, just no active session
|
|
182
190
|
pass
|
|
183
191
|
else:
|
|
@@ -199,11 +207,7 @@ class DeviceISEView(generic.ObjectView):
|
|
|
199
207
|
|
|
200
208
|
# If IP lookup failed or no IP, try hostname
|
|
201
209
|
# Use VC name for virtual chassis members (original hostname)
|
|
202
|
-
lookup_hostname =
|
|
203
|
-
device.virtual_chassis.name
|
|
204
|
-
if device.virtual_chassis
|
|
205
|
-
else device.name
|
|
206
|
-
)
|
|
210
|
+
lookup_hostname = device.virtual_chassis.name if device.virtual_chassis else device.name
|
|
207
211
|
if ("error" in ise_data or not ise_data) and lookup_hostname:
|
|
208
212
|
ise_data = client.get_network_device_by_name(lookup_hostname)
|
|
209
213
|
|
|
@@ -218,21 +222,22 @@ class DeviceISEView(generic.ObjectView):
|
|
|
218
222
|
|
|
219
223
|
# Choose template based on lookup type
|
|
220
224
|
if ise_data.get("is_nad"):
|
|
221
|
-
template = "netbox_cisco_ise/
|
|
225
|
+
template = "netbox_cisco_ise/nad_tab_content.html"
|
|
222
226
|
else:
|
|
223
|
-
template =
|
|
224
|
-
|
|
225
|
-
return
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
template = "netbox_cisco_ise/endpoint_tab_content.html"
|
|
228
|
+
|
|
229
|
+
return HttpResponse(
|
|
230
|
+
render_to_string(
|
|
231
|
+
template,
|
|
232
|
+
{
|
|
233
|
+
"object": device,
|
|
234
|
+
"ise_data": ise_data,
|
|
235
|
+
"session_data": session_data,
|
|
236
|
+
"error": error,
|
|
237
|
+
"ise_url": ise_url,
|
|
238
|
+
},
|
|
239
|
+
request=request,
|
|
240
|
+
)
|
|
236
241
|
)
|
|
237
242
|
|
|
238
243
|
|
|
@@ -276,3 +281,130 @@ class TestConnectionView(View):
|
|
|
276
281
|
return JsonResponse(result, status=400)
|
|
277
282
|
|
|
278
283
|
return JsonResponse(result)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# Endpoint-specific functions for netbox_endpoints plugin
|
|
287
|
+
def should_show_ise_tab_endpoint(endpoint):
|
|
288
|
+
"""
|
|
289
|
+
Determine if the ISE tab should be visible for this endpoint.
|
|
290
|
+
|
|
291
|
+
Shows tab if endpoint has a MAC address and matches configured endpoint_mappings
|
|
292
|
+
(or if endpoint_mappings is empty, show for all endpoints with MAC).
|
|
293
|
+
"""
|
|
294
|
+
if not ENDPOINTS_PLUGIN_INSTALLED:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
# Must have MAC address for ISE lookup
|
|
298
|
+
if not endpoint.mac_address:
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
config = settings.PLUGINS_CONFIG.get("netbox_cisco_ise", {})
|
|
302
|
+
mappings = config.get("endpoint_mappings", [])
|
|
303
|
+
|
|
304
|
+
# If no mappings configured, show for all endpoints with MAC
|
|
305
|
+
if not mappings:
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
# Check if endpoint matches any mapping
|
|
309
|
+
if not endpoint.endpoint_type:
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
manufacturer = endpoint.endpoint_type.manufacturer
|
|
313
|
+
manufacturer_slug = manufacturer.slug.lower() if manufacturer and manufacturer.slug else ""
|
|
314
|
+
manufacturer_name = manufacturer.name.lower() if manufacturer and manufacturer.name else ""
|
|
315
|
+
endpoint_type_slug = endpoint.endpoint_type.slug.lower() if endpoint.endpoint_type.slug else ""
|
|
316
|
+
endpoint_type_model = endpoint.endpoint_type.model.lower() if endpoint.endpoint_type.model else ""
|
|
317
|
+
|
|
318
|
+
for mapping in mappings:
|
|
319
|
+
manufacturer_pattern = mapping.get("manufacturer", "").lower()
|
|
320
|
+
endpoint_type_pattern = mapping.get("endpoint_type", "").lower()
|
|
321
|
+
|
|
322
|
+
# Check manufacturer match
|
|
323
|
+
manufacturer_match = False
|
|
324
|
+
if manufacturer_pattern:
|
|
325
|
+
try:
|
|
326
|
+
if re.search(manufacturer_pattern, manufacturer_slug, re.IGNORECASE) or re.search(
|
|
327
|
+
manufacturer_pattern, manufacturer_name, re.IGNORECASE
|
|
328
|
+
):
|
|
329
|
+
manufacturer_match = True
|
|
330
|
+
except re.error:
|
|
331
|
+
if manufacturer_pattern in manufacturer_slug or manufacturer_pattern in manufacturer_name:
|
|
332
|
+
manufacturer_match = True
|
|
333
|
+
|
|
334
|
+
if not manufacturer_match:
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
# Check endpoint_type match if specified
|
|
338
|
+
if endpoint_type_pattern:
|
|
339
|
+
endpoint_type_match = False
|
|
340
|
+
try:
|
|
341
|
+
if re.search(endpoint_type_pattern, endpoint_type_slug, re.IGNORECASE) or re.search(
|
|
342
|
+
endpoint_type_pattern, endpoint_type_model, re.IGNORECASE
|
|
343
|
+
):
|
|
344
|
+
endpoint_type_match = True
|
|
345
|
+
except re.error:
|
|
346
|
+
if endpoint_type_pattern in endpoint_type_slug or endpoint_type_pattern in endpoint_type_model:
|
|
347
|
+
endpoint_type_match = True
|
|
348
|
+
|
|
349
|
+
if not endpoint_type_match:
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
# Mapping matches
|
|
353
|
+
return True
|
|
354
|
+
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# Endpoint views - only available if netbox_endpoints is installed
|
|
359
|
+
if ENDPOINTS_PLUGIN_INSTALLED:
|
|
360
|
+
|
|
361
|
+
class EndpointISEContentView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
362
|
+
"""HTMX endpoint that returns ISE content for netbox Endpoint async loading."""
|
|
363
|
+
|
|
364
|
+
permission_required = "netbox_endpoints.view_endpoint"
|
|
365
|
+
|
|
366
|
+
def get(self, request, pk):
|
|
367
|
+
"""Fetch ISE data and return HTML content."""
|
|
368
|
+
endpoint = Endpoint.objects.select_related("endpoint_type__manufacturer").get(pk=pk)
|
|
369
|
+
|
|
370
|
+
client = get_client()
|
|
371
|
+
config = settings.PLUGINS_CONFIG.get("netbox_cisco_ise", {})
|
|
372
|
+
|
|
373
|
+
ise_data = {}
|
|
374
|
+
session_data = {}
|
|
375
|
+
error = None
|
|
376
|
+
|
|
377
|
+
if not client:
|
|
378
|
+
error = "Cisco ISE not configured. Configure the plugin in NetBox settings."
|
|
379
|
+
elif not endpoint.mac_address:
|
|
380
|
+
error = "No MAC address configured for this endpoint."
|
|
381
|
+
else:
|
|
382
|
+
# Endpoint lookup by MAC address
|
|
383
|
+
mac_address = str(endpoint.mac_address)
|
|
384
|
+
ise_data = client.get_endpoint_by_mac(mac_address)
|
|
385
|
+
if "error" not in ise_data:
|
|
386
|
+
# Also get session data for connected endpoints
|
|
387
|
+
session_data = client.get_active_session_by_mac(mac_address)
|
|
388
|
+
if "error" in session_data and session_data.get("connected") is False:
|
|
389
|
+
# Not connected is fine, just no active session
|
|
390
|
+
pass
|
|
391
|
+
else:
|
|
392
|
+
error = ise_data.get("error")
|
|
393
|
+
ise_data = {}
|
|
394
|
+
|
|
395
|
+
# Get ISE URL for external links
|
|
396
|
+
ise_url = config.get("ise_url", "").rstrip("/")
|
|
397
|
+
|
|
398
|
+
return HttpResponse(
|
|
399
|
+
render_to_string(
|
|
400
|
+
"netbox_cisco_ise/endpoint_tab_content.html",
|
|
401
|
+
{
|
|
402
|
+
"object": endpoint,
|
|
403
|
+
"ise_data": ise_data,
|
|
404
|
+
"session_data": session_data,
|
|
405
|
+
"error": error,
|
|
406
|
+
"ise_url": ise_url,
|
|
407
|
+
},
|
|
408
|
+
request=request,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-cisco-ise
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: NetBox plugin for Cisco ISE integration - endpoint tracking, NAD management, and session visibility
|
|
5
5
|
Author-email: sieteunoseis <jeremy.worden@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -273,6 +273,12 @@ Contributions are welcome! Please:
|
|
|
273
273
|
2. Create a feature branch
|
|
274
274
|
3. Submit a pull request
|
|
275
275
|
|
|
276
|
+
## Support
|
|
277
|
+
|
|
278
|
+
If you find this plugin helpful, consider supporting development:
|
|
279
|
+
|
|
280
|
+
[](https://buymeacoffee.com/automatebldrs)
|
|
281
|
+
|
|
276
282
|
## Related Projects
|
|
277
283
|
|
|
278
284
|
- [netbox-catalyst-center](https://github.com/sieteunoseis/netbox-catalyst-center) - Catalyst Center integration for NetBox
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
netbox_cisco_ise/__init__.py,sha256=ZSzSxpy3uFtG427NwWnYD_fZt0_TGRirS0GZxR_KJsc,5267
|
|
2
|
+
netbox_cisco_ise/ise_client.py,sha256=sFK2UUYN8tvUpxjQxdd6BbU5H01NF3E65kQUAzI69k0,16291
|
|
3
|
+
netbox_cisco_ise/navigation.py,sha256=mMN4EyzZl6ehoqflLeYy7logt39wpCN2TEsPfqn1VtI,507
|
|
4
|
+
netbox_cisco_ise/urls.py,sha256=qlIlqkj4jYeDSiWnMDl7dGfoQTvWgigbahUOPUA4tkU,712
|
|
5
|
+
netbox_cisco_ise/views.py,sha256=oay16jnmS-ZWhT9XYSnvbRvWYEoTvZ1s8-wHaczZfF4,15031
|
|
6
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/device_tab.html,sha256=H8xS-cwU-oOCS7Sgo3RMD01UDnqrIKR2KU3G-GyR9O0,775
|
|
7
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/endpoint_tab.html,sha256=aS7R0oTjkUmqlP7i2zfrFhYs9F-LknJgcomGGK60u7w,9868
|
|
8
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/endpoint_tab_content.html,sha256=Cn3JDGVLcUv7u9pgK867nDdLBPPdY1ViGsr5KewDuRE,9795
|
|
9
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/nad_tab.html,sha256=Y5rCGyoSqzRmfridJp2BmC3HkPGRhvWdZ3Mp-C81qYk,8783
|
|
10
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/nad_tab_content.html,sha256=0KyCQ1E1hTC3gD_T4FuC_Ll8cuPVVji_ofqCCrTJxHk,8710
|
|
11
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/netbox_endpoint_tab.html,sha256=fMHwjCz_dXa-nSelZdFP0LLpj62WAh1EU-eaMY6xEps,786
|
|
12
|
+
netbox_cisco_ise/templates/netbox_cisco_ise/settings.html,sha256=ZsnJ_AclJUDa-h7vJTd5Clh7pc3OOBx6Tf9Y_bx3oIE,8953
|
|
13
|
+
netbox_cisco_ise-0.1.7.dist-info/licenses/LICENSE,sha256=KmjHs19UP3vo7K2IWXkq3JDKG9PatSbqeLPwu3o2k7g,10761
|
|
14
|
+
netbox_cisco_ise-0.1.7.dist-info/METADATA,sha256=yxm1emfd_6dqWy_gDbLF6Pl5HwlSWUfdmBSomzx37vM,9099
|
|
15
|
+
netbox_cisco_ise-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
16
|
+
netbox_cisco_ise-0.1.7.dist-info/top_level.txt,sha256=LMP1ppZRzqtdaMGzz53KgacW_PEwyLSM9wwIMuBvJ00,17
|
|
17
|
+
netbox_cisco_ise-0.1.7.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
netbox_cisco_ise/__init__.py,sha256=RNZeW8KihZEVJpBGTL_6SBkjgt1tdSeVirRGrE9MC90,2203
|
|
2
|
-
netbox_cisco_ise/ise_client.py,sha256=sfeC-cLv0JWo_NdxZG9M4VEGNyLYCVymB6iItGCUvH8,16403
|
|
3
|
-
netbox_cisco_ise/navigation.py,sha256=mMN4EyzZl6ehoqflLeYy7logt39wpCN2TEsPfqn1VtI,507
|
|
4
|
-
netbox_cisco_ise/urls.py,sha256=3tJHJyEQXYZ2WXw4zq1kds7xpgyHl1-HwVHlgtJA84E,304
|
|
5
|
-
netbox_cisco_ise/views.py,sha256=pT8dSh_1LN1PjDkTHEePukUvShXsy49dRr8Mmhudcgw,9422
|
|
6
|
-
netbox_cisco_ise/templates/netbox_cisco_ise/endpoint_tab.html,sha256=aS7R0oTjkUmqlP7i2zfrFhYs9F-LknJgcomGGK60u7w,9868
|
|
7
|
-
netbox_cisco_ise/templates/netbox_cisco_ise/nad_tab.html,sha256=Y5rCGyoSqzRmfridJp2BmC3HkPGRhvWdZ3Mp-C81qYk,8783
|
|
8
|
-
netbox_cisco_ise/templates/netbox_cisco_ise/settings.html,sha256=ZsnJ_AclJUDa-h7vJTd5Clh7pc3OOBx6Tf9Y_bx3oIE,8953
|
|
9
|
-
netbox_cisco_ise-0.1.4.dist-info/licenses/LICENSE,sha256=KmjHs19UP3vo7K2IWXkq3JDKG9PatSbqeLPwu3o2k7g,10761
|
|
10
|
-
netbox_cisco_ise-0.1.4.dist-info/METADATA,sha256=Q4Iu1gbIBW87E0SwjkbA6TWguH6MnBMOt2CONuPola0,8859
|
|
11
|
-
netbox_cisco_ise-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
-
netbox_cisco_ise-0.1.4.dist-info/top_level.txt,sha256=LMP1ppZRzqtdaMGzz53KgacW_PEwyLSM9wwIMuBvJ00,17
|
|
13
|
-
netbox_cisco_ise-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|