netbox-toolkit-plugin 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_toolkit_plugin/__init__.py +32 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/serializers.py +71 -35
- {netbox_toolkit → netbox_toolkit_plugin}/api/urls.py +3 -3
- {netbox_toolkit → netbox_toolkit_plugin}/config.py +80 -73
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/factory.py +170 -111
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/netmiko_connector.py +242 -179
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/scrapli_connector.py +256 -172
- netbox_toolkit_plugin/migrations/0001_initial.py +108 -0
- netbox_toolkit_plugin/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +70 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0003_permission_system_update.py +26 -12
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0004_remove_django_permissions.py +27 -29
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0005_alter_command_options_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0007_alter_commandlog_parsing_template.py +6 -4
- {netbox_toolkit → netbox_toolkit_plugin}/models.py +31 -32
- {netbox_toolkit → netbox_toolkit_plugin}/navigation.py +6 -6
- {netbox_toolkit → netbox_toolkit_plugin}/services/command_service.py +188 -128
- {netbox_toolkit → netbox_toolkit_plugin}/services/rate_limiting_service.py +104 -97
- netbox_toolkit_plugin/tables.py +51 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command.html +108 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command_list.html +12 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/commandlog.html +170 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/device_toolkit.html +557 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command.html +5 -5
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_list.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/device_toolkit.html +6 -6
- netbox_toolkit_plugin/urls.py +38 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/logging.py +20 -19
- {netbox_toolkit → netbox_toolkit_plugin}/views.py +251 -169
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.1.dist-info}/METADATA +2 -2
- netbox_toolkit_plugin-0.1.1.dist-info/RECORD +60 -0
- netbox_toolkit_plugin-0.1.1.dist-info/entry_points.txt +2 -0
- netbox_toolkit_plugin-0.1.1.dist-info/top_level.txt +1 -0
- netbox_toolkit/__init__.py +0 -30
- netbox_toolkit/migrations/0001_initial.py +0 -54
- netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +0 -66
- netbox_toolkit/tables.py +0 -37
- netbox_toolkit/urls.py +0 -22
- netbox_toolkit_plugin-0.1.0.dist-info/RECORD +0 -56
- netbox_toolkit_plugin-0.1.0.dist-info/entry_points.txt +0 -2
- netbox_toolkit_plugin-0.1.0.dist-info/top_level.txt +0 -1
- {netbox_toolkit → netbox_toolkit_plugin}/admin.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/mixins.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/schemas.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/command_logs.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/commands.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/base.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/exceptions.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/filtersets.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/forms.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/search.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/device_service.py +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/css/toolkit.css +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/js/toolkit.js +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_edit.html +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog_list.html +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/connection.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/error_parser.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/network.py +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.1.dist-info}/WHEEL +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,557 @@
|
|
1
|
+
{% extends 'dcim/device.html' %}
|
2
|
+
{% load helpers %}
|
3
|
+
{% load static %}
|
4
|
+
|
5
|
+
{% block style %}
|
6
|
+
<link href="{% static 'netbox_toolkit_plugin/css/toolkit.css' %}" rel="stylesheet">
|
7
|
+
{% endblock %}
|
8
|
+
|
9
|
+
{% block content %}
|
10
|
+
<div class="row">
|
11
|
+
<!-- Left Column with Commands -->
|
12
|
+
<div class="col-md-3 d-flex flex-column">
|
13
|
+
<!-- Device Connection Info -->
|
14
|
+
<div class="card mb-3">
|
15
|
+
<div class="card-header">
|
16
|
+
<h3 class="card-title">Connection Info</h3>
|
17
|
+
<div class="card-actions">
|
18
|
+
<button class="btn btn-icon" type="button" data-bs-toggle="collapse"
|
19
|
+
data-bs-target="#connectionInfoCollapse" aria-expanded="true"
|
20
|
+
aria-controls="connectionInfoCollapse">
|
21
|
+
<i class="mdi mdi-chevron-up collapse-icon"></i>
|
22
|
+
</button>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<div class="collapse show" id="connectionInfoCollapse">
|
26
|
+
<div class="card-body">
|
27
|
+
<table class="table table-sm">
|
28
|
+
<tbody>
|
29
|
+
<tr>
|
30
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
31
|
+
style="width: 40%; padding: 0.5rem 0;">Hostname / IP</td>
|
32
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
33
|
+
<div class="font-monospace text-xs">
|
34
|
+
{% if connection_info.hostname %}
|
35
|
+
{{ connection_info.hostname }}
|
36
|
+
{% else %}
|
37
|
+
<span class="text-danger fst-italic">Missing</span>
|
38
|
+
{% endif %}
|
39
|
+
</div>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
<tr>
|
43
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
44
|
+
style="width: 40%; padding: 0.5rem 0;">Platform</td>
|
45
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
46
|
+
<div class="font-monospace text-xs">
|
47
|
+
{% if connection_info.platform %}
|
48
|
+
{{ connection_info.platform }}
|
49
|
+
{% else %}
|
50
|
+
<span class="text-danger fst-italic">Missing</span>
|
51
|
+
{% endif %}
|
52
|
+
</div>
|
53
|
+
</td>
|
54
|
+
</tr>
|
55
|
+
<tr>
|
56
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
57
|
+
style="width: 40%; padding: 0.5rem 0;">Connection</td>
|
58
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
59
|
+
<div class="font-monospace text-xs">
|
60
|
+
{% if device_valid %}
|
61
|
+
<span class="text-success">Ready</span>
|
62
|
+
{% else %}
|
63
|
+
<span class="text-danger">Not Ready</span>
|
64
|
+
{% endif %}
|
65
|
+
</div>
|
66
|
+
</td>
|
67
|
+
</tr>
|
68
|
+
</tbody>
|
69
|
+
</table>
|
70
|
+
<div class="pt-3 border-top mt-2">
|
71
|
+
<small class="text-muted">
|
72
|
+
<i class="mdi mdi-information-outline me-1"></i>
|
73
|
+
{% if device_valid %}
|
74
|
+
Device is ready for command execution.
|
75
|
+
{% else %}
|
76
|
+
{{ validation_message }}
|
77
|
+
{% endif %}
|
78
|
+
</small>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
|
84
|
+
<!-- Rate Limiting Status -->
|
85
|
+
{% if rate_limit_status.enabled %}
|
86
|
+
<div class="card mt-3">
|
87
|
+
<div class="card-header">
|
88
|
+
<h3 class="card-title">Rate Limiting</h3>
|
89
|
+
<div class="card-actions">
|
90
|
+
<button class="btn btn-icon" type="button" data-bs-toggle="collapse"
|
91
|
+
data-bs-target="#rateLimitCollapse" aria-expanded="true" aria-controls="rateLimitCollapse">
|
92
|
+
<i class="mdi mdi-chevron-up collapse-icon"></i>
|
93
|
+
</button>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
<div class="collapse show" id="rateLimitCollapse">
|
97
|
+
<div class="card-body">
|
98
|
+
{% if rate_limit_status.bypassed %}
|
99
|
+
<div class="alert alert-info mb-3">
|
100
|
+
<i class="mdi mdi-shield-check me-2"></i>
|
101
|
+
<strong>Rate Limiting Bypassed</strong><br>
|
102
|
+
<small>You have unlimited command execution privileges.</small>
|
103
|
+
</div>
|
104
|
+
{% else %}
|
105
|
+
<table class="table table-sm">
|
106
|
+
<tbody>
|
107
|
+
<tr>
|
108
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
109
|
+
style="width: 40%; padding: 0.5rem 0;">Commands Remaining</td>
|
110
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
111
|
+
<div class="font-monospace text-xs">
|
112
|
+
<span
|
113
|
+
class="{% if rate_limit_status.is_exceeded %}text-danger{% elif rate_limit_status.is_warning %}text-warning{% else %}text-success{% endif %}">
|
114
|
+
{{ rate_limit_status.remaining }}
|
115
|
+
</span>
|
116
|
+
</div>
|
117
|
+
</td>
|
118
|
+
</tr>
|
119
|
+
<tr>
|
120
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
121
|
+
style="width: 40%; padding: 0.5rem 0;">Successful Commands Used</td>
|
122
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
123
|
+
<div class="font-monospace text-xs">{{ rate_limit_status.current_count }} / {{
|
124
|
+
rate_limit_status.limit }}</div>
|
125
|
+
</td>
|
126
|
+
</tr>
|
127
|
+
<tr>
|
128
|
+
<td class="align-middle border-0 fw-semibold text-muted"
|
129
|
+
style="width: 40%; padding: 0.5rem 0;">Time Window</td>
|
130
|
+
<td class="align-middle border-0" style="width: 60%; padding: 0.5rem 0;">
|
131
|
+
<div class="font-monospace text-xs">{{ rate_limit_status.time_window_minutes }}
|
132
|
+
minutes</div>
|
133
|
+
</td>
|
134
|
+
</tr>
|
135
|
+
</tbody>
|
136
|
+
</table>
|
137
|
+
<div class="pt-3 border-top mt-2">
|
138
|
+
<small class="text-muted">
|
139
|
+
<i class="mdi mdi-clock-outline me-1"></i>
|
140
|
+
{{ rate_limit_status.message }}
|
141
|
+
</small>
|
142
|
+
</div>
|
143
|
+
|
144
|
+
{% if rate_limit_status.is_exceeded %}
|
145
|
+
<div class="alert alert-danger mt-3 mb-0">
|
146
|
+
<i class="mdi mdi-block-helper me-2"></i>
|
147
|
+
<strong>Rate Limit Exceeded</strong><br>
|
148
|
+
<small>Command execution is blocked. Wait for the time window to reset or contact an
|
149
|
+
administrator.</small>
|
150
|
+
</div>
|
151
|
+
{% elif rate_limit_status.is_warning %}
|
152
|
+
<div class="alert alert-warning mt-3 mb-0">
|
153
|
+
<i class="mdi mdi-alert-outline me-2"></i>
|
154
|
+
<strong>Rate Limit Warning</strong><br>
|
155
|
+
<small>You are approaching the command execution limit. Commands will be blocked if the limit is
|
156
|
+
exceeded.</small>
|
157
|
+
</div>
|
158
|
+
{% endif %}
|
159
|
+
{% endif %}
|
160
|
+
</div>
|
161
|
+
</div>
|
162
|
+
</div>
|
163
|
+
{% endif %}
|
164
|
+
|
165
|
+
<!-- Commands List -->
|
166
|
+
<div class="card flex-grow-1">
|
167
|
+
<div class="card-header">
|
168
|
+
<h3 class="card-title">Available Commands</h3>
|
169
|
+
{% if command_count is not None and total_command_count != command_count %}
|
170
|
+
<div class="card-subtitle">
|
171
|
+
<small class="text-muted">
|
172
|
+
<i class="mdi mdi-information-outline"></i>
|
173
|
+
Showing {{ command_count }} of {{ total_command_count }} based on your permissions
|
174
|
+
</small>
|
175
|
+
</div>
|
176
|
+
{% endif %}
|
177
|
+
</div>
|
178
|
+
<div class="card-body card-commands p-1">
|
179
|
+
{% if commands %}
|
180
|
+
<div class="list-group list-group-flush">
|
181
|
+
{% for command in commands %}
|
182
|
+
<div class="list-group-item list-group-item-action command-item" data-command-id="{{ command.id }}"
|
183
|
+
data-command-name="{{ command.name }}" title="{{ command.description }}">
|
184
|
+
<div class="d-flex justify-content-between align-items-center">
|
185
|
+
<div class="d-flex align-items-center">
|
186
|
+
{% if command.command_type == 'config' %}
|
187
|
+
<i class="mdi mdi-alert-outline text-danger me-2 opacity-75" style="font-size: 1.25rem;"
|
188
|
+
title="Configuration command - use with caution"></i>
|
189
|
+
{% endif %}
|
190
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_detail' pk=command.pk %}"
|
191
|
+
class="text-decoration-none text-body">
|
192
|
+
{{ command.name }}
|
193
|
+
</a>
|
194
|
+
</div>
|
195
|
+
<div class="d-flex align-items-center">
|
196
|
+
<!-- Run button (hidden by default, shown on hover) -->
|
197
|
+
<button type="button" class="btn btn-sm btn-success command-run-btn"
|
198
|
+
title="Execute command" data-command-id="{{ command.id }}"
|
199
|
+
data-command-name="{{ command.name }}">
|
200
|
+
<i class="mdi mdi-play me-1"></i>Run
|
201
|
+
</button>
|
202
|
+
</div>
|
203
|
+
</div>
|
204
|
+
</div>
|
205
|
+
{% endfor %}
|
206
|
+
</div>
|
207
|
+
{% else %}
|
208
|
+
<div class="alert alert-info">
|
209
|
+
{% if object.platform %}
|
210
|
+
{% if not user.is_authenticated %}
|
211
|
+
You must be logged in to see commands.
|
212
|
+
{% else %}
|
213
|
+
No commands available for platform "{{ object.platform }}" with your current permissions.
|
214
|
+
{% endif %}
|
215
|
+
{% else %}
|
216
|
+
No platform assigned to this device.
|
217
|
+
{% endif %}
|
218
|
+
{% if perms.netbox_toolkit_plugin.add_command %}
|
219
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_add' %}" class="alert-link">
|
220
|
+
Add a command
|
221
|
+
</a>
|
222
|
+
{% endif %}
|
223
|
+
</div>
|
224
|
+
{% endif %}
|
225
|
+
</div>
|
226
|
+
</div>
|
227
|
+
|
228
|
+
<!-- Recent Command History -->
|
229
|
+
<div class="card mt-3">
|
230
|
+
<div class="card-header">
|
231
|
+
<h3 class="card-title">Recent History</h3>
|
232
|
+
<div class="card-actions">
|
233
|
+
<small class="text-muted">Last 3</small>
|
234
|
+
</div>
|
235
|
+
</div>
|
236
|
+
<div class="card-body p-2">
|
237
|
+
{% if object.command_logs.all %}
|
238
|
+
<div class="list-group list-group-flush">
|
239
|
+
{% for log in object.command_logs.all|dictsortreversed:"execution_time"|slice:":3" %}
|
240
|
+
<div class="list-group-item px-0 py-1 border-0">
|
241
|
+
<div class="d-flex flex-column">
|
242
|
+
<div class="text-truncate" style="font-size: 0.85rem; line-height: 1.2;"
|
243
|
+
title="{{ log.command }}">
|
244
|
+
<strong>{{ log.command|truncatechars:40 }}</strong>
|
245
|
+
</div>
|
246
|
+
<div class="d-flex justify-content-between align-items-center mt-1">
|
247
|
+
<small class="text-muted text-truncate" style="flex-shrink: 1;">{{ log.username
|
248
|
+
}}</small>
|
249
|
+
<small class="text-muted" style="flex-shrink: 0; margin-left: 8px;">{{
|
250
|
+
log.execution_time|timesince }} ago</small>
|
251
|
+
</div>
|
252
|
+
</div>
|
253
|
+
</div>
|
254
|
+
{% endfor %}
|
255
|
+
</div>
|
256
|
+
{% else %}
|
257
|
+
<div class="text-center text-muted py-2">
|
258
|
+
<small>No history available</small>
|
259
|
+
</div>
|
260
|
+
{% endif %}
|
261
|
+
</div>
|
262
|
+
</div>
|
263
|
+
</div>
|
264
|
+
|
265
|
+
<!-- Right Column with Output -->
|
266
|
+
<div class="col-md-9">
|
267
|
+
<div class="card h-100">
|
268
|
+
<div class="card-header">
|
269
|
+
<h3 class="card-title">Command Output</h3>
|
270
|
+
{% if executed_command %}
|
271
|
+
<div class="card-actions">
|
272
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_detail' pk=executed_command.pk %}"
|
273
|
+
class="text-decoration-none text-muted" title="View command details">
|
274
|
+
{{ executed_command.name }}
|
275
|
+
<i class="mdi mdi-open-in-new ms-1" style="font-size: 0.8rem;"></i>
|
276
|
+
</a>
|
277
|
+
</div>
|
278
|
+
{% endif %}
|
279
|
+
</div>
|
280
|
+
<div class="card-body">
|
281
|
+
<div id="commandOutputContainer">
|
282
|
+
{% if command_output %}
|
283
|
+
{% if execution_success %}
|
284
|
+
<!-- Successful command output -->
|
285
|
+
<div class="alert alert-success d-flex align-items-start mb-3">
|
286
|
+
<i class="mdi mdi-check-circle me-2 mt-1"></i>
|
287
|
+
<div>
|
288
|
+
<strong>Command executed successfully</strong>
|
289
|
+
{% if execution_time %}
|
290
|
+
<br><small class="text-muted">Execution time: {{ execution_time|floatformat:3 }}s</small>
|
291
|
+
{% endif %}
|
292
|
+
</div>
|
293
|
+
</div>
|
294
|
+
|
295
|
+
<!-- Tabbed output interface -->
|
296
|
+
<div class="card">
|
297
|
+
<div class="card-header">
|
298
|
+
<ul class="nav nav-tabs nav-fill" id="outputTabs" role="tablist">
|
299
|
+
<li class="nav-item" role="presentation">
|
300
|
+
<button class="nav-link active" id="raw-output-tab" data-bs-toggle="tab"
|
301
|
+
data-bs-target="#raw-output" type="button" role="tab" aria-controls="raw-output"
|
302
|
+
aria-selected="true">
|
303
|
+
<i class="mdi mdi-console me-1"></i>
|
304
|
+
Raw Output
|
305
|
+
</button>
|
306
|
+
</li>
|
307
|
+
{% if parsed_data %}
|
308
|
+
<li class="nav-item" role="presentation">
|
309
|
+
<button class="nav-link" id="parsed-data-tab" data-bs-toggle="tab"
|
310
|
+
data-bs-target="#parsed-data" type="button" role="tab"
|
311
|
+
aria-controls="parsed-data" aria-selected="false">
|
312
|
+
<i class="mdi mdi-table me-1"></i>
|
313
|
+
Parsed Data
|
314
|
+
</button>
|
315
|
+
</li>
|
316
|
+
{% endif %}
|
317
|
+
</ul>
|
318
|
+
</div>
|
319
|
+
<div class="card-body p-0">
|
320
|
+
<div class="tab-content" id="outputTabContent">
|
321
|
+
<!-- Raw Output Tab -->
|
322
|
+
<div class="tab-pane fade show active" id="raw-output" role="tabpanel"
|
323
|
+
aria-labelledby="raw-output-tab">
|
324
|
+
<div
|
325
|
+
class="d-flex justify-content-between align-items-start py-3 px-3 border-bottom">
|
326
|
+
<h6 class="mb-0 mt-1">Command Output</h6>
|
327
|
+
<div class="btn-list">
|
328
|
+
<button type="button" class="btn btn-sm btn-outline-primary copy-output-btn"
|
329
|
+
title="Copy raw output to clipboard">
|
330
|
+
<i class="mdi mdi-content-copy me-1"></i>Copy
|
331
|
+
</button>
|
332
|
+
</div>
|
333
|
+
</div>
|
334
|
+
<div class="p-3">
|
335
|
+
<pre
|
336
|
+
class="command-output bg-surface p-3 rounded font-monospace">{{ command_output }}</pre>
|
337
|
+
</div>
|
338
|
+
</div>
|
339
|
+
|
340
|
+
<!-- Parsed Data Tab -->
|
341
|
+
{% if parsed_data %}
|
342
|
+
<div class="tab-pane fade" id="parsed-data" role="tabpanel"
|
343
|
+
aria-labelledby="parsed-data-tab">
|
344
|
+
<div
|
345
|
+
class="d-flex justify-content-between align-items-start py-3 px-3 border-bottom">
|
346
|
+
<div class="mt-1">
|
347
|
+
<h6 class="mb-0">Structured Data</h6>
|
348
|
+
{% if parsing_template %}
|
349
|
+
<small class="text-muted">Template: {{ parsing_template }}</small>
|
350
|
+
{% endif %}
|
351
|
+
</div>
|
352
|
+
{% if parsed_data|length > 0 %}
|
353
|
+
{{ parsed_data|json_script:"parsed-data-json" }}
|
354
|
+
<div class="btn-list">
|
355
|
+
<button type="button" class="btn btn-sm btn-outline-primary copy-parsed-btn"
|
356
|
+
title="Copy parsed data as JSON">
|
357
|
+
<i class="mdi mdi-content-copy me-1"></i>Copy JSON
|
358
|
+
</button>
|
359
|
+
</div>
|
360
|
+
{% endif %}
|
361
|
+
</div>
|
362
|
+
<div class="p-3">
|
363
|
+
{% if parsed_data|length > 0 %}
|
364
|
+
{% if parsed_data.0 %}
|
365
|
+
<!-- Table format for structured data -->
|
366
|
+
<div class="table-responsive">
|
367
|
+
<table class="table table-sm table-striped mb-0">
|
368
|
+
<thead>
|
369
|
+
<tr>
|
370
|
+
{% for key in parsed_data.0.keys %}
|
371
|
+
<th>{{ key|title }}</th>
|
372
|
+
{% endfor %}
|
373
|
+
</tr>
|
374
|
+
</thead>
|
375
|
+
<tbody>
|
376
|
+
{% for row in parsed_data %}
|
377
|
+
<tr>
|
378
|
+
{% for value in row.values %}
|
379
|
+
<td>{{ value }}</td>
|
380
|
+
{% endfor %}
|
381
|
+
</tr>
|
382
|
+
{% endfor %}
|
383
|
+
</tbody>
|
384
|
+
</table>
|
385
|
+
</div>
|
386
|
+
{% else %}
|
387
|
+
<!-- JSON format for other data types -->
|
388
|
+
<pre class="bg-light p-3 rounded mb-0">{{ parsed_data|pprint }}</pre>
|
389
|
+
{% endif %}
|
390
|
+
{% else %}
|
391
|
+
<div class="alert alert-info mb-0">
|
392
|
+
<i class="mdi mdi-information-outline me-1"></i>
|
393
|
+
No structured data found in the output.
|
394
|
+
</div>
|
395
|
+
{% endif %}
|
396
|
+
</div>
|
397
|
+
</div>
|
398
|
+
{% endif %}
|
399
|
+
</div>
|
400
|
+
</div>
|
401
|
+
</div>
|
402
|
+
{% elif has_syntax_error %}
|
403
|
+
<!-- Syntax error output -->
|
404
|
+
<div class="alert alert-warning alert-dismissible" role="alert">
|
405
|
+
<div class="d-flex">
|
406
|
+
<div>
|
407
|
+
<svg class="icon alert-icon" width="24" height="24">
|
408
|
+
<use xlink:href="#tabler-alert-triangle"></use>
|
409
|
+
</svg>
|
410
|
+
</div>
|
411
|
+
<div>
|
412
|
+
<h4 class="alert-title">Command executed with syntax error detected</h4>
|
413
|
+
{% if syntax_error_type and syntax_error_vendor %}
|
414
|
+
<div class="text-secondary">Error Type: {{ syntax_error_type|title }} | Vendor: {{
|
415
|
+
syntax_error_vendor|title }}</div>
|
416
|
+
{% endif %}
|
417
|
+
</div>
|
418
|
+
</div>
|
419
|
+
</div>
|
420
|
+
<div class="card">
|
421
|
+
<div class="card-header bg-warning-subtle">
|
422
|
+
<h6 class="card-title mb-0">
|
423
|
+
<i class="mdi mdi-alert-outline me-1"></i>
|
424
|
+
Syntax Error Details and Guidance
|
425
|
+
</h6>
|
426
|
+
</div>
|
427
|
+
<div class="card-body">
|
428
|
+
<pre class="command-output font-monospace">{{ command_output }}</pre>
|
429
|
+
</div>
|
430
|
+
</div>
|
431
|
+
{% else %}
|
432
|
+
<!-- Error output -->
|
433
|
+
<div class="alert alert-danger alert-dismissible" role="alert">
|
434
|
+
<div class="d-flex">
|
435
|
+
<div>
|
436
|
+
<svg class="icon alert-icon" width="24" height="24">
|
437
|
+
<use xlink:href="#tabler-alert-circle"></use>
|
438
|
+
</svg>
|
439
|
+
</div>
|
440
|
+
<div>
|
441
|
+
<h4 class="alert-title">Command execution failed</h4>
|
442
|
+
<div class="text-secondary">Check the error details below for troubleshooting
|
443
|
+
information.</div>
|
444
|
+
</div>
|
445
|
+
</div>
|
446
|
+
</div>
|
447
|
+
<div class="card">
|
448
|
+
<div class="card-header bg-danger-subtle">
|
449
|
+
<h6 class="card-title mb-0">
|
450
|
+
<i class="mdi mdi-information-outline me-1"></i>
|
451
|
+
Error Details and Troubleshooting
|
452
|
+
</h6>
|
453
|
+
</div>
|
454
|
+
<div class="card-body">
|
455
|
+
<pre class="command-output font-monospace">{{ command_output }}</pre>
|
456
|
+
</div>
|
457
|
+
</div>
|
458
|
+
{% endif %}
|
459
|
+
{% else %}
|
460
|
+
<div class="alert alert-info" id="defaultMessage">
|
461
|
+
Select a command to execute from the list on the left.
|
462
|
+
</div>
|
463
|
+
{% endif %}
|
464
|
+
</div>
|
465
|
+
</div>
|
466
|
+
</div>
|
467
|
+
</div>
|
468
|
+
</div>
|
469
|
+
|
470
|
+
<!-- Credential and Confirmation Modal -->
|
471
|
+
<div class="modal fade credential-modal" id="credentialModal" tabindex="-1" aria-labelledby="credentialModalLabel"
|
472
|
+
aria-hidden="true">
|
473
|
+
<div class="modal-dialog">
|
474
|
+
<div class="modal-content">
|
475
|
+
<div class="modal-header">
|
476
|
+
<h5 class="modal-title" id="credentialModalLabel">Execute Command</h5>
|
477
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
478
|
+
</div>
|
479
|
+
<div class="modal-body">
|
480
|
+
<div class="mb-3">
|
481
|
+
<p class="text-muted mb-3">
|
482
|
+
<i class="mdi mdi-information-outline me-1"></i>
|
483
|
+
Enter your credentials to execute: <strong id="commandToExecute"></strong>
|
484
|
+
</p>
|
485
|
+
</div>
|
486
|
+
|
487
|
+
<!-- Rate Limiting Information in Modal -->
|
488
|
+
{% if rate_limit_status.enabled and not rate_limit_status.bypassed %}
|
489
|
+
<div
|
490
|
+
class="alert {% if rate_limit_status.is_exceeded %}alert-danger{% elif rate_limit_status.is_warning %}alert-warning{% else %}alert-info{% endif %} mb-3">
|
491
|
+
<div class="d-flex align-items-start">
|
492
|
+
<i
|
493
|
+
class="mdi {% if rate_limit_status.is_exceeded %}mdi-block-helper{% elif rate_limit_status.is_warning %}mdi-alert-outline{% else %}mdi-information-outline{% endif %} me-2 mt-1"></i>
|
494
|
+
<div>
|
495
|
+
<small>
|
496
|
+
<strong>Rate Limiting:</strong> {{ rate_limit_status.remaining }} commands remaining
|
497
|
+
({{ rate_limit_status.current_count }}/{{ rate_limit_status.limit }} successful commands
|
498
|
+
used)
|
499
|
+
{% if rate_limit_status.is_exceeded %}
|
500
|
+
<br><span class="text-danger">Error: Rate limit exceeded! Commands are blocked.</span>
|
501
|
+
{% elif rate_limit_status.is_warning %}
|
502
|
+
<br><span class="text-warning">Warning: Approaching command limit!</span>
|
503
|
+
{% endif %}
|
504
|
+
</small>
|
505
|
+
</div>
|
506
|
+
</div>
|
507
|
+
</div>
|
508
|
+
{% endif %}
|
509
|
+
|
510
|
+
<form id="modalCredentialsForm">
|
511
|
+
<div class="mb-3">
|
512
|
+
<label for="modalUsername" class="form-label">Username</label>
|
513
|
+
<input type="text" id="modalUsername" class="form-control" required autocomplete="username">
|
514
|
+
</div>
|
515
|
+
<div class="mb-3">
|
516
|
+
<label for="modalPassword" class="form-label">Password</label>
|
517
|
+
<input type="password" id="modalPassword" class="form-control" required
|
518
|
+
autocomplete="current-password">
|
519
|
+
</div>
|
520
|
+
<div class="alert alert-warning">
|
521
|
+
<i class="mdi mdi-shield-lock-outline me-1"></i>
|
522
|
+
<small>Credentials are not stored and are required for each command execution.</small>
|
523
|
+
</div>
|
524
|
+
</form>
|
525
|
+
</div>
|
526
|
+
<div class="modal-footer">
|
527
|
+
<div class="btn-list w-100 justify-content-end">
|
528
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
529
|
+
<button type="button" class="btn btn-primary" id="executeCommandBtn" {% if rate_limit_status.enabled
|
530
|
+
and not rate_limit_status.bypassed and rate_limit_status.is_exceeded %}disabled{% endif %}>
|
531
|
+
<i class="mdi mdi-play me-1"></i>
|
532
|
+
{% if rate_limit_status.enabled and not rate_limit_status.bypassed and
|
533
|
+
rate_limit_status.is_exceeded %}
|
534
|
+
Rate Limited
|
535
|
+
{% else %}
|
536
|
+
Execute Command
|
537
|
+
{% endif %}
|
538
|
+
</button>
|
539
|
+
</div>
|
540
|
+
</div>
|
541
|
+
</div>
|
542
|
+
</div>
|
543
|
+
</div>
|
544
|
+
|
545
|
+
<form id="commandExecutionForm" method="post" style="display: none;">
|
546
|
+
{% csrf_token %}
|
547
|
+
<input type="hidden" name="command_id" id="selectedCommandId">
|
548
|
+
<input type="hidden" name="username" id="formUsername">
|
549
|
+
<input type="hidden" name="password" id="formPassword">
|
550
|
+
</form>
|
551
|
+
|
552
|
+
{% endblock %}
|
553
|
+
|
554
|
+
{% block javascript %}
|
555
|
+
<!-- Load NetBox Toolkit consolidated JavaScript -->
|
556
|
+
<script src="{% static 'netbox_toolkit_plugin/js/toolkit.js' %}"></script>
|
557
|
+
{% endblock %}
|
@@ -3,17 +3,17 @@
|
|
3
3
|
{% load static %}
|
4
4
|
|
5
5
|
{% block style %}
|
6
|
-
<link href="{% static '
|
6
|
+
<link href="{% static 'netbox_toolkit_plugin/css/toolkit.css' %}" rel="stylesheet">
|
7
7
|
{% endblock %}
|
8
8
|
|
9
9
|
{% block buttons %}
|
10
|
-
{% if perms.
|
11
|
-
<a href="{% url 'plugins:
|
10
|
+
{% if perms.netbox_toolkit_plugin.change_command %}
|
11
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_edit' pk=object.pk %}" class="btn btn-warning">
|
12
12
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
13
13
|
</a>
|
14
14
|
{% endif %}
|
15
|
-
{% if perms.
|
16
|
-
<a href="{% url 'plugins:
|
15
|
+
{% if perms.netbox_toolkit_plugin.delete_command %}
|
16
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_delete' pk=object.pk %}" class="btn btn-danger">
|
17
17
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
18
18
|
</a>
|
19
19
|
{% endif %}
|
@@ -4,8 +4,8 @@
|
|
4
4
|
{% block title %}Commands{% endblock %}
|
5
5
|
|
6
6
|
{% block buttons %}
|
7
|
-
{% if perms.
|
8
|
-
<a href="{% url 'plugins:
|
7
|
+
{% if perms.netbox_toolkit_plugin.add_command %}
|
8
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_add' %}" class="btn btn-primary">
|
9
9
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Command
|
10
10
|
</a>
|
11
11
|
{% endif %}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
{% load static %}
|
4
4
|
|
5
5
|
{% block style %}
|
6
|
-
<link href="{% static '
|
6
|
+
<link href="{% static 'netbox_toolkit_plugin/css/toolkit.css' %}" rel="stylesheet">
|
7
7
|
{% endblock %}
|
8
8
|
|
9
9
|
{% block content %}
|
@@ -166,5 +166,5 @@
|
|
166
166
|
|
167
167
|
{% block javascript %}
|
168
168
|
<!-- Load NetBox Toolkit consolidated JavaScript -->
|
169
|
-
<script src="{% static '
|
169
|
+
<script src="{% static 'netbox_toolkit_plugin/js/toolkit.js' %}"></script>
|
170
170
|
{% endblock %}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
{% load static %}
|
4
4
|
|
5
5
|
{% block style %}
|
6
|
-
<link href="{% static '
|
6
|
+
<link href="{% static 'netbox_toolkit_plugin/css/toolkit.css' %}" rel="stylesheet">
|
7
7
|
{% endblock %}
|
8
8
|
|
9
9
|
{% block content %}
|
@@ -179,7 +179,7 @@
|
|
179
179
|
style="font-size: 1.25rem;"
|
180
180
|
title="Configuration command - use with caution"></i>
|
181
181
|
{% endif %}
|
182
|
-
<a href="{% url 'plugins:
|
182
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_detail' pk=command.pk %}"
|
183
183
|
class="text-decoration-none text-body">
|
184
184
|
{{ command.name }}
|
185
185
|
</a>
|
@@ -209,8 +209,8 @@
|
|
209
209
|
{% else %}
|
210
210
|
No platform assigned to this device.
|
211
211
|
{% endif %}
|
212
|
-
{% if perms.
|
213
|
-
<a href="{% url 'plugins:
|
212
|
+
{% if perms.netbox_toolkit_plugin.add_command %}
|
213
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_add' %}" class="alert-link">
|
214
214
|
Add a command
|
215
215
|
</a>
|
216
216
|
{% endif %}
|
@@ -260,7 +260,7 @@
|
|
260
260
|
<h3 class="card-title">Command Output</h3>
|
261
261
|
{% if executed_command %}
|
262
262
|
<div class="card-actions">
|
263
|
-
<a href="{% url 'plugins:
|
263
|
+
<a href="{% url 'plugins:netbox_toolkit_plugin:command_detail' pk=executed_command.pk %}"
|
264
264
|
class="text-decoration-none text-muted"
|
265
265
|
title="View command details">
|
266
266
|
{{ executed_command.name }}
|
@@ -532,5 +532,5 @@
|
|
532
532
|
|
533
533
|
{% block javascript %}
|
534
534
|
<!-- Load NetBox Toolkit consolidated JavaScript -->
|
535
|
-
<script src="{% static '
|
535
|
+
<script src="{% static 'netbox_toolkit_plugin/js/toolkit.js' %}"></script>
|
536
536
|
{% endblock %}
|