netbox-toolkit-plugin 0.1.1__py3-none-any.whl → 0.1.3__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 +1 -1
- netbox_toolkit_plugin/admin.py +11 -7
- netbox_toolkit_plugin/api/mixins.py +20 -16
- netbox_toolkit_plugin/api/schemas.py +53 -74
- netbox_toolkit_plugin/api/serializers.py +10 -11
- netbox_toolkit_plugin/api/urls.py +2 -1
- netbox_toolkit_plugin/api/views/__init__.py +4 -3
- netbox_toolkit_plugin/api/views/command_logs.py +80 -73
- netbox_toolkit_plugin/api/views/commands.py +140 -134
- netbox_toolkit_plugin/connectors/__init__.py +9 -9
- netbox_toolkit_plugin/connectors/base.py +30 -31
- netbox_toolkit_plugin/connectors/factory.py +22 -26
- netbox_toolkit_plugin/connectors/netmiko_connector.py +18 -28
- netbox_toolkit_plugin/connectors/scrapli_connector.py +17 -16
- netbox_toolkit_plugin/exceptions.py +0 -7
- netbox_toolkit_plugin/filtersets.py +26 -42
- netbox_toolkit_plugin/forms.py +13 -11
- netbox_toolkit_plugin/migrations/0008_remove_parsed_data_storage.py +26 -0
- netbox_toolkit_plugin/models.py +2 -17
- netbox_toolkit_plugin/navigation.py +3 -0
- netbox_toolkit_plugin/search.py +12 -9
- netbox_toolkit_plugin/services/__init__.py +1 -1
- netbox_toolkit_plugin/services/command_service.py +7 -10
- netbox_toolkit_plugin/services/device_service.py +40 -32
- netbox_toolkit_plugin/services/rate_limiting_service.py +4 -3
- netbox_toolkit_plugin/{config.py → settings.py} +17 -7
- netbox_toolkit_plugin/static/netbox_toolkit_plugin/js/toolkit.js +245 -119
- netbox_toolkit_plugin/tables.py +10 -1
- netbox_toolkit_plugin/templates/netbox_toolkit_plugin/commandlog.html +16 -84
- netbox_toolkit_plugin/templates/netbox_toolkit_plugin/device_toolkit.html +37 -33
- netbox_toolkit_plugin/urls.py +10 -3
- netbox_toolkit_plugin/utils/connection.py +54 -54
- netbox_toolkit_plugin/utils/error_parser.py +128 -109
- netbox_toolkit_plugin/utils/logging.py +1 -0
- netbox_toolkit_plugin/utils/network.py +74 -47
- netbox_toolkit_plugin/views.py +51 -22
- {netbox_toolkit_plugin-0.1.1.dist-info → netbox_toolkit_plugin-0.1.3.dist-info}/METADATA +2 -2
- netbox_toolkit_plugin-0.1.3.dist-info/RECORD +61 -0
- netbox_toolkit_plugin-0.1.1.dist-info/RECORD +0 -60
- {netbox_toolkit_plugin-0.1.1.dist-info → netbox_toolkit_plugin-0.1.3.dist-info}/WHEEL +0 -0
- {netbox_toolkit_plugin-0.1.1.dist-info → netbox_toolkit_plugin-0.1.3.dist-info}/entry_points.txt +0 -0
- {netbox_toolkit_plugin-0.1.1.dist-info → netbox_toolkit_plugin-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {netbox_toolkit_plugin-0.1.1.dist-info → netbox_toolkit_plugin-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* NetBox Toolkit Plugin - Consolidated JavaScript
|
3
|
-
*
|
3
|
+
*
|
4
4
|
* This file contains all the JavaScript functionality for the NetBox Toolkit plugin
|
5
5
|
* to avoid duplication across templates and improve maintainability.
|
6
6
|
*/
|
@@ -8,15 +8,15 @@
|
|
8
8
|
// Namespace for the toolkit functionality
|
9
9
|
window.NetBoxToolkit = window.NetBoxToolkit || {};
|
10
10
|
|
11
|
-
(function(Toolkit) {
|
11
|
+
(function (Toolkit) {
|
12
12
|
'use strict';
|
13
|
-
|
13
|
+
|
14
14
|
// Prevent multiple initialization
|
15
15
|
if (Toolkit.initialized) {
|
16
16
|
console.log('NetBox Toolkit already initialized, skipping');
|
17
17
|
return;
|
18
18
|
}
|
19
|
-
|
19
|
+
|
20
20
|
/**
|
21
21
|
* Utility functions
|
22
22
|
*/
|
@@ -24,16 +24,16 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
24
24
|
/**
|
25
25
|
* Show success state on a button temporarily
|
26
26
|
*/
|
27
|
-
showButtonSuccess: function(btn, successText = '<i class="mdi mdi-check me-1"></i>Copied!', duration = 2000) {
|
27
|
+
showButtonSuccess: function (btn, successText = '<i class="mdi mdi-check me-1"></i>Copied!', duration = 2000) {
|
28
28
|
const originalText = btn.innerHTML;
|
29
29
|
const originalClass = btn.className;
|
30
|
-
|
30
|
+
|
31
31
|
btn.classList.add('copied');
|
32
32
|
btn.innerHTML = successText;
|
33
33
|
btn.style.backgroundColor = 'var(--tblr-success)';
|
34
34
|
btn.style.borderColor = 'var(--tblr-success)';
|
35
35
|
btn.style.color = 'white';
|
36
|
-
|
36
|
+
|
37
37
|
setTimeout(() => {
|
38
38
|
btn.className = originalClass;
|
39
39
|
btn.innerHTML = originalText;
|
@@ -42,18 +42,18 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
42
42
|
btn.style.color = '';
|
43
43
|
}, duration);
|
44
44
|
},
|
45
|
-
|
45
|
+
|
46
46
|
/**
|
47
47
|
* Fallback text copy using document.execCommand (legacy browsers)
|
48
48
|
*/
|
49
|
-
fallbackCopyText: function(text, btn) {
|
49
|
+
fallbackCopyText: function (text, btn) {
|
50
50
|
const textArea = document.createElement('textarea');
|
51
51
|
textArea.value = text;
|
52
52
|
textArea.style.position = 'fixed';
|
53
53
|
textArea.style.left = '-999999px';
|
54
54
|
textArea.style.top = '-999999px';
|
55
55
|
document.body.appendChild(textArea);
|
56
|
-
|
56
|
+
|
57
57
|
try {
|
58
58
|
textArea.focus();
|
59
59
|
textArea.select();
|
@@ -72,7 +72,7 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
72
72
|
}
|
73
73
|
}
|
74
74
|
};
|
75
|
-
|
75
|
+
|
76
76
|
/**
|
77
77
|
* Copy functionality for both parsed data and raw output
|
78
78
|
* Works with multiple element ID patterns for flexibility
|
@@ -81,45 +81,51 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
81
81
|
/**
|
82
82
|
* Initialize copy functionality for both parsed data and raw output buttons
|
83
83
|
*/
|
84
|
-
init: function() {
|
84
|
+
init: function () {
|
85
85
|
// Initialize parsed data copy buttons
|
86
86
|
const copyParsedBtns = document.querySelectorAll('.copy-parsed-btn');
|
87
87
|
copyParsedBtns.forEach(btn => {
|
88
88
|
btn.addEventListener('click', this.handleCopyParsedData.bind(this));
|
89
89
|
});
|
90
|
-
|
90
|
+
|
91
91
|
// Initialize raw output copy buttons
|
92
92
|
const copyOutputBtns = document.querySelectorAll('.copy-output-btn');
|
93
93
|
copyOutputBtns.forEach(btn => {
|
94
94
|
btn.addEventListener('click', this.handleCopyRawOutput.bind(this));
|
95
95
|
});
|
96
|
+
|
97
|
+
// Initialize CSV download buttons
|
98
|
+
const downloadCsvBtns = document.querySelectorAll('.download-csv-btn');
|
99
|
+
downloadCsvBtns.forEach(btn => {
|
100
|
+
btn.addEventListener('click', this.handleDownloadCSV.bind(this));
|
101
|
+
});
|
96
102
|
},
|
97
|
-
|
103
|
+
|
98
104
|
/**
|
99
105
|
* Handle copying raw command output from pre elements
|
100
106
|
*/
|
101
|
-
handleCopyRawOutput: function(event) {
|
107
|
+
handleCopyRawOutput: function (event) {
|
102
108
|
const btn = event.target.closest('.copy-output-btn');
|
103
109
|
if (!btn) return;
|
104
|
-
|
110
|
+
|
105
111
|
// Find the command output element
|
106
112
|
// Look for .command-output within the same tab pane or nearby
|
107
113
|
const tabPane = btn.closest('.tab-pane') || btn.closest('.card-body') || document;
|
108
114
|
const outputElement = tabPane.querySelector('.command-output');
|
109
|
-
|
115
|
+
|
110
116
|
if (!outputElement) {
|
111
117
|
console.error('No command output element found');
|
112
118
|
alert('No command output found to copy');
|
113
119
|
return;
|
114
120
|
}
|
115
|
-
|
121
|
+
|
116
122
|
const outputText = outputElement.textContent || outputElement.innerText;
|
117
123
|
if (!outputText || !outputText.trim()) {
|
118
124
|
console.error('No command output text found');
|
119
125
|
alert('No command output available to copy');
|
120
126
|
return;
|
121
127
|
}
|
122
|
-
|
128
|
+
|
123
129
|
// Use modern Clipboard API if available
|
124
130
|
if (navigator.clipboard && window.isSecureContext) {
|
125
131
|
navigator.clipboard.writeText(outputText.trim()).then(() => {
|
@@ -133,44 +139,44 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
133
139
|
Toolkit.Utils.fallbackCopyText(outputText.trim(), btn);
|
134
140
|
}
|
135
141
|
},
|
136
|
-
|
142
|
+
|
137
143
|
/**
|
138
144
|
* Handle copying parsed data from JSON script elements
|
139
145
|
*/
|
140
|
-
handleCopyParsedData: function(event) {
|
146
|
+
handleCopyParsedData: function (event) {
|
141
147
|
const btn = event.target.closest('.copy-parsed-btn');
|
142
148
|
if (!btn) return;
|
143
|
-
|
149
|
+
|
144
150
|
// Try multiple possible element IDs for parsed data
|
145
151
|
const possibleIds = [
|
146
152
|
'parsed-data-json', // device_toolkit.html
|
147
153
|
'commandlog-parsed-data-json' // commandlog.html
|
148
154
|
];
|
149
|
-
|
155
|
+
|
150
156
|
let parsedDataElement = null;
|
151
157
|
for (const id of possibleIds) {
|
152
158
|
parsedDataElement = document.getElementById(id);
|
153
159
|
if (parsedDataElement) break;
|
154
160
|
}
|
155
|
-
|
161
|
+
|
156
162
|
if (!parsedDataElement) {
|
157
163
|
console.error('No parsed data script element found with IDs:', possibleIds);
|
158
164
|
alert('No parsed data found to copy');
|
159
165
|
return;
|
160
166
|
}
|
161
|
-
|
167
|
+
|
162
168
|
const parsedDataStr = parsedDataElement.textContent;
|
163
169
|
if (!parsedDataStr) {
|
164
170
|
console.error('No parsed data found to copy');
|
165
171
|
alert('No parsed data available');
|
166
172
|
return;
|
167
173
|
}
|
168
|
-
|
174
|
+
|
169
175
|
try {
|
170
176
|
// Parse and re-stringify for clean formatting
|
171
177
|
const parsedData = JSON.parse(parsedDataStr);
|
172
178
|
const formattedJson = JSON.stringify(parsedData, null, 2);
|
173
|
-
|
179
|
+
|
174
180
|
// Use modern Clipboard API if available
|
175
181
|
if (navigator.clipboard && window.isSecureContext) {
|
176
182
|
navigator.clipboard.writeText(formattedJson).then(() => {
|
@@ -187,22 +193,142 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
187
193
|
console.error('Error processing parsed data:', err);
|
188
194
|
alert('Failed to process parsed data for copying: ' + err.message);
|
189
195
|
}
|
196
|
+
},
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Handle downloading parsed data as CSV
|
200
|
+
*/
|
201
|
+
handleDownloadCSV: function (event) {
|
202
|
+
const btn = event.target.closest('.download-csv-btn');
|
203
|
+
if (!btn) return;
|
204
|
+
|
205
|
+
// Try multiple possible element IDs for parsed data
|
206
|
+
const possibleIds = [
|
207
|
+
'parsed-data-json', // device_toolkit.html
|
208
|
+
'commandlog-parsed-data-json' // commandlog.html
|
209
|
+
];
|
210
|
+
|
211
|
+
let parsedDataElement = null;
|
212
|
+
for (const id of possibleIds) {
|
213
|
+
parsedDataElement = document.getElementById(id);
|
214
|
+
if (parsedDataElement) break;
|
215
|
+
}
|
216
|
+
|
217
|
+
if (!parsedDataElement) {
|
218
|
+
console.error('No parsed data script element found with IDs:', possibleIds);
|
219
|
+
alert('No parsed data found to download');
|
220
|
+
return;
|
221
|
+
}
|
222
|
+
|
223
|
+
const parsedDataStr = parsedDataElement.textContent;
|
224
|
+
if (!parsedDataStr) {
|
225
|
+
console.error('No parsed data found to download');
|
226
|
+
alert('No parsed data available');
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
|
230
|
+
try {
|
231
|
+
const parsedData = JSON.parse(parsedDataStr);
|
232
|
+
const csvContent = this.convertToCSV(parsedData);
|
233
|
+
|
234
|
+
// Create and trigger download
|
235
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
236
|
+
const link = document.createElement('a');
|
237
|
+
|
238
|
+
if (link.download !== undefined) { // Feature detection
|
239
|
+
const url = URL.createObjectURL(blob);
|
240
|
+
link.setAttribute('href', url);
|
241
|
+
link.setAttribute('download', 'parsed_data.csv');
|
242
|
+
link.style.visibility = 'hidden';
|
243
|
+
document.body.appendChild(link);
|
244
|
+
link.click();
|
245
|
+
document.body.removeChild(link);
|
246
|
+
|
247
|
+
// Show success feedback
|
248
|
+
Toolkit.Utils.showButtonSuccess(btn, '<i class="mdi mdi-check me-1"></i>Downloaded!');
|
249
|
+
} else {
|
250
|
+
// Fallback for older browsers
|
251
|
+
alert('CSV download not supported in your browser');
|
252
|
+
}
|
253
|
+
} catch (err) {
|
254
|
+
console.error('Error processing parsed data for CSV:', err);
|
255
|
+
alert('Failed to process parsed data for CSV download: ' + err.message);
|
256
|
+
}
|
257
|
+
},
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Convert JSON data to CSV format
|
261
|
+
*/
|
262
|
+
convertToCSV: function (data) {
|
263
|
+
if (!data) return '';
|
264
|
+
|
265
|
+
// Handle different types of data
|
266
|
+
if (Array.isArray(data) && data.length > 0) {
|
267
|
+
if (typeof data[0] === 'object' && data[0] !== null) {
|
268
|
+
// Array of objects - structured data
|
269
|
+
const headers = Object.keys(data[0]);
|
270
|
+
const csvRows = [];
|
271
|
+
|
272
|
+
// Add header row
|
273
|
+
csvRows.push(headers.map(header => this.escapeCSVField(header)).join(','));
|
274
|
+
|
275
|
+
// Add data rows
|
276
|
+
data.forEach(row => {
|
277
|
+
const values = headers.map(header => {
|
278
|
+
const value = row[header];
|
279
|
+
return this.escapeCSVField(value);
|
280
|
+
});
|
281
|
+
csvRows.push(values.join(','));
|
282
|
+
});
|
283
|
+
|
284
|
+
return csvRows.join('\n');
|
285
|
+
} else {
|
286
|
+
// Array of simple values
|
287
|
+
return 'Value\n' + data.map(item => this.escapeCSVField(item)).join('\n');
|
288
|
+
}
|
289
|
+
} else if (typeof data === 'object' && data !== null) {
|
290
|
+
// Single object - convert to key-value pairs
|
291
|
+
const csvRows = ['Key,Value'];
|
292
|
+
Object.entries(data).forEach(([key, value]) => {
|
293
|
+
csvRows.push(`${this.escapeCSVField(key)},${this.escapeCSVField(value)}`);
|
294
|
+
});
|
295
|
+
return csvRows.join('\n');
|
296
|
+
} else {
|
297
|
+
// Simple value
|
298
|
+
return 'Data\n' + this.escapeCSVField(data);
|
299
|
+
}
|
300
|
+
},
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Escape CSV field values
|
304
|
+
*/
|
305
|
+
escapeCSVField: function (field) {
|
306
|
+
if (field === null || field === undefined) return '';
|
307
|
+
|
308
|
+
const stringField = String(field);
|
309
|
+
|
310
|
+
// If field contains comma, quote, or newline, wrap in quotes and escape quotes
|
311
|
+
if (stringField.includes(',') || stringField.includes('"') || stringField.includes('\n') || stringField.includes('\r')) {
|
312
|
+
return '"' + stringField.replace(/"/g, '""') + '"';
|
313
|
+
}
|
314
|
+
|
315
|
+
return stringField;
|
190
316
|
}
|
191
317
|
};
|
192
|
-
|
318
|
+
|
193
319
|
/**
|
194
320
|
* Modal management for device toolkit
|
195
321
|
*/
|
196
322
|
Toolkit.ModalManager = {
|
197
323
|
instance: null,
|
198
|
-
|
324
|
+
|
199
325
|
/**
|
200
326
|
* Initialize modal functionality
|
201
327
|
*/
|
202
|
-
init: function() {
|
328
|
+
init: function () {
|
203
329
|
const credentialModal = document.getElementById('credentialModal');
|
204
330
|
if (!credentialModal) return;
|
205
|
-
|
331
|
+
|
206
332
|
// Try Bootstrap modal first, fallback to manual control
|
207
333
|
try {
|
208
334
|
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
@@ -216,56 +342,56 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
216
342
|
console.error('Bootstrap modal initialization failed:', error);
|
217
343
|
this.instance = this.createManualModal(credentialModal);
|
218
344
|
}
|
219
|
-
|
345
|
+
|
220
346
|
this.setupModalEvents(credentialModal);
|
221
347
|
},
|
222
|
-
|
348
|
+
|
223
349
|
/**
|
224
350
|
* Create manual modal controls for fallback
|
225
351
|
*/
|
226
|
-
createManualModal: function(modalElement) {
|
352
|
+
createManualModal: function (modalElement) {
|
227
353
|
return {
|
228
|
-
show: function() {
|
354
|
+
show: function () {
|
229
355
|
modalElement.style.display = 'block';
|
230
356
|
modalElement.classList.add('show');
|
231
357
|
document.body.classList.add('modal-open');
|
232
|
-
|
358
|
+
|
233
359
|
// Create backdrop
|
234
360
|
const backdrop = document.createElement('div');
|
235
361
|
backdrop.className = 'modal-backdrop fade show';
|
236
362
|
backdrop.id = 'credentialModalBackdrop';
|
237
363
|
document.body.appendChild(backdrop);
|
238
|
-
|
364
|
+
|
239
365
|
// Trigger shown event
|
240
366
|
const shownEvent = new Event('shown.bs.modal');
|
241
367
|
modalElement.dispatchEvent(shownEvent);
|
242
368
|
},
|
243
|
-
hide: function() {
|
369
|
+
hide: function () {
|
244
370
|
modalElement.style.display = 'none';
|
245
371
|
modalElement.classList.remove('show');
|
246
372
|
document.body.classList.remove('modal-open');
|
247
|
-
|
373
|
+
|
248
374
|
// Remove backdrop
|
249
375
|
const backdrop = document.getElementById('credentialModalBackdrop');
|
250
376
|
if (backdrop) {
|
251
377
|
backdrop.remove();
|
252
378
|
}
|
253
|
-
|
379
|
+
|
254
380
|
// Trigger hidden event
|
255
381
|
const hiddenEvent = new Event('hidden.bs.modal');
|
256
382
|
modalElement.dispatchEvent(hiddenEvent);
|
257
383
|
}
|
258
384
|
};
|
259
385
|
},
|
260
|
-
|
386
|
+
|
261
387
|
/**
|
262
388
|
* Setup modal event handlers
|
263
389
|
*/
|
264
|
-
setupModalEvents: function(credentialModal) {
|
390
|
+
setupModalEvents: function (credentialModal) {
|
265
391
|
// Handle close button clicks
|
266
392
|
const modalCloseButton = credentialModal.querySelector('.btn-close');
|
267
393
|
const modalCancelButton = credentialModal.querySelector('.btn-secondary');
|
268
|
-
|
394
|
+
|
269
395
|
if (modalCloseButton) {
|
270
396
|
modalCloseButton.addEventListener('click', (event) => {
|
271
397
|
event.preventDefault();
|
@@ -274,7 +400,7 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
274
400
|
}
|
275
401
|
});
|
276
402
|
}
|
277
|
-
|
403
|
+
|
278
404
|
if (modalCancelButton) {
|
279
405
|
modalCancelButton.addEventListener('click', (event) => {
|
280
406
|
event.preventDefault();
|
@@ -283,7 +409,7 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
283
409
|
}
|
284
410
|
});
|
285
411
|
}
|
286
|
-
|
412
|
+
|
287
413
|
// Handle backdrop clicks for manual modal
|
288
414
|
credentialModal.addEventListener('click', (event) => {
|
289
415
|
if (event.target === credentialModal && this.instance) {
|
@@ -291,112 +417,112 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
291
417
|
}
|
292
418
|
});
|
293
419
|
},
|
294
|
-
|
420
|
+
|
295
421
|
/**
|
296
422
|
* Show the modal
|
297
423
|
*/
|
298
|
-
show: function() {
|
424
|
+
show: function () {
|
299
425
|
if (this.instance) {
|
300
426
|
this.instance.show();
|
301
427
|
}
|
302
428
|
},
|
303
|
-
|
429
|
+
|
304
430
|
/**
|
305
431
|
* Hide the modal
|
306
432
|
*/
|
307
|
-
hide: function() {
|
433
|
+
hide: function () {
|
308
434
|
if (this.instance) {
|
309
435
|
this.instance.hide();
|
310
436
|
}
|
311
437
|
}
|
312
438
|
};
|
313
|
-
|
439
|
+
|
314
440
|
/**
|
315
441
|
* Command execution functionality for device toolkit
|
316
442
|
*/
|
317
443
|
Toolkit.CommandManager = {
|
318
444
|
currentCommandData: null,
|
319
|
-
|
445
|
+
|
320
446
|
/**
|
321
447
|
* Initialize command execution functionality
|
322
448
|
*/
|
323
|
-
init: function() {
|
449
|
+
init: function () {
|
324
450
|
// Initialize modal first
|
325
451
|
Toolkit.ModalManager.init();
|
326
|
-
|
452
|
+
|
327
453
|
// Setup collapse toggle for connection info
|
328
454
|
this.setupConnectionInfoToggle();
|
329
|
-
|
455
|
+
|
330
456
|
// Setup command execution
|
331
457
|
this.setupCommandExecution();
|
332
|
-
|
458
|
+
|
333
459
|
// Setup modal form handlers
|
334
460
|
this.setupModalForm();
|
335
461
|
},
|
336
|
-
|
462
|
+
|
337
463
|
/**
|
338
464
|
* Setup connection info collapse toggle
|
339
465
|
*/
|
340
|
-
setupConnectionInfoToggle: function() {
|
466
|
+
setupConnectionInfoToggle: function () {
|
341
467
|
const connectionInfoCollapse = document.getElementById('connectionInfoCollapse');
|
342
468
|
const connectionInfoToggleButton = document.querySelector('[data-bs-target="#connectionInfoCollapse"]');
|
343
|
-
|
469
|
+
|
344
470
|
if (connectionInfoCollapse && connectionInfoToggleButton) {
|
345
471
|
connectionInfoCollapse.addEventListener('hidden.bs.collapse', function () {
|
346
472
|
connectionInfoToggleButton.classList.add('collapsed');
|
347
473
|
});
|
348
|
-
|
474
|
+
|
349
475
|
connectionInfoCollapse.addEventListener('shown.bs.collapse', function () {
|
350
476
|
connectionInfoToggleButton.classList.remove('collapsed');
|
351
477
|
});
|
352
478
|
}
|
353
479
|
},
|
354
|
-
|
480
|
+
|
355
481
|
/**
|
356
482
|
* Setup command execution event handlers
|
357
483
|
*/
|
358
|
-
setupCommandExecution: function() {
|
484
|
+
setupCommandExecution: function () {
|
359
485
|
const commandContainer = document.querySelector('.card-commands');
|
360
486
|
if (!commandContainer) {
|
361
487
|
console.error('Command container not found');
|
362
488
|
return;
|
363
489
|
}
|
364
|
-
|
490
|
+
|
365
491
|
// Use event delegation to avoid duplicate listeners
|
366
492
|
commandContainer.removeEventListener('click', this.handleRunButtonClick.bind(this));
|
367
493
|
commandContainer.addEventListener('click', this.handleRunButtonClick.bind(this));
|
368
494
|
},
|
369
|
-
|
495
|
+
|
370
496
|
/**
|
371
497
|
* Handle run button clicks
|
372
498
|
*/
|
373
|
-
handleRunButtonClick: function(event) {
|
499
|
+
handleRunButtonClick: function (event) {
|
374
500
|
// Only handle clicks on run buttons
|
375
501
|
const runButton = event.target.closest('.command-run-btn');
|
376
502
|
if (!runButton) return;
|
377
|
-
|
503
|
+
|
378
504
|
// Prevent any other event handlers from running
|
379
505
|
event.preventDefault();
|
380
506
|
event.stopImmediatePropagation();
|
381
|
-
|
507
|
+
|
382
508
|
console.log('Run button clicked:', runButton);
|
383
|
-
|
509
|
+
|
384
510
|
// Prevent double-clicks by checking if already processing
|
385
511
|
if (runButton.dataset.processing === 'true') {
|
386
512
|
console.log('Command already processing, ignoring click');
|
387
513
|
return;
|
388
514
|
}
|
389
|
-
|
515
|
+
|
390
516
|
// Get the command item and set active state
|
391
517
|
const commandItem = runButton.closest('.command-item');
|
392
518
|
const allCommandItems = document.querySelectorAll('.command-item');
|
393
519
|
allCommandItems.forEach(ci => ci.classList.remove('active'));
|
394
520
|
commandItem.classList.add('active');
|
395
|
-
|
521
|
+
|
396
522
|
// Store command data for modal
|
397
523
|
const commandId = runButton.getAttribute('data-command-id');
|
398
524
|
const commandName = runButton.getAttribute('data-command-name');
|
399
|
-
|
525
|
+
|
400
526
|
this.currentCommandData = {
|
401
527
|
id: commandId,
|
402
528
|
name: commandName,
|
@@ -404,22 +530,22 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
404
530
|
button: runButton,
|
405
531
|
originalIconClass: runButton.querySelector('i').className
|
406
532
|
};
|
407
|
-
|
533
|
+
|
408
534
|
// Update modal content
|
409
535
|
const commandToExecuteElement = document.getElementById('commandToExecute');
|
410
536
|
if (commandToExecuteElement) {
|
411
537
|
commandToExecuteElement.textContent = commandName;
|
412
538
|
}
|
413
|
-
|
539
|
+
|
414
540
|
// Clear previous credentials and show modal
|
415
541
|
const modalUsernameField = document.getElementById('modalUsername');
|
416
542
|
const modalPasswordField = document.getElementById('modalPassword');
|
417
543
|
if (modalUsernameField) modalUsernameField.value = '';
|
418
544
|
if (modalPasswordField) modalPasswordField.value = '';
|
419
|
-
|
545
|
+
|
420
546
|
// Show the modal
|
421
547
|
Toolkit.ModalManager.show();
|
422
|
-
|
548
|
+
|
423
549
|
// Focus on username field when modal is shown
|
424
550
|
const credentialModal = document.getElementById('credentialModal');
|
425
551
|
if (credentialModal && modalUsernameField) {
|
@@ -428,21 +554,21 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
428
554
|
}, { once: true });
|
429
555
|
}
|
430
556
|
},
|
431
|
-
|
557
|
+
|
432
558
|
/**
|
433
559
|
* Setup modal form handlers
|
434
560
|
*/
|
435
|
-
setupModalForm: function() {
|
561
|
+
setupModalForm: function () {
|
436
562
|
const executeCommandBtn = document.getElementById('executeCommandBtn');
|
437
563
|
const modalUsernameField = document.getElementById('modalUsername');
|
438
564
|
const modalPasswordField = document.getElementById('modalPassword');
|
439
565
|
const credentialModal = document.getElementById('credentialModal');
|
440
|
-
|
566
|
+
|
441
567
|
if (!executeCommandBtn) return;
|
442
|
-
|
568
|
+
|
443
569
|
// Handle execute button click in modal
|
444
570
|
executeCommandBtn.addEventListener('click', this.executeCommand.bind(this));
|
445
|
-
|
571
|
+
|
446
572
|
// Handle Enter key in modal form
|
447
573
|
if (modalPasswordField) {
|
448
574
|
modalPasswordField.addEventListener('keypress', (event) => {
|
@@ -452,7 +578,7 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
452
578
|
}
|
453
579
|
});
|
454
580
|
}
|
455
|
-
|
581
|
+
|
456
582
|
if (modalUsernameField) {
|
457
583
|
modalUsernameField.addEventListener('keypress', (event) => {
|
458
584
|
if (event.key === 'Enter') {
|
@@ -463,7 +589,7 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
463
589
|
}
|
464
590
|
});
|
465
591
|
}
|
466
|
-
|
592
|
+
|
467
593
|
// Clean up when modal is hidden
|
468
594
|
if (credentialModal) {
|
469
595
|
credentialModal.addEventListener('hidden.bs.modal', () => {
|
@@ -476,63 +602,63 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
476
602
|
});
|
477
603
|
}
|
478
604
|
},
|
479
|
-
|
605
|
+
|
480
606
|
/**
|
481
607
|
* Execute the selected command
|
482
608
|
*/
|
483
|
-
executeCommand: function() {
|
609
|
+
executeCommand: function () {
|
484
610
|
const modalUsernameField = document.getElementById('modalUsername');
|
485
611
|
const modalPasswordField = document.getElementById('modalPassword');
|
486
|
-
|
612
|
+
|
487
613
|
// Validate credentials
|
488
614
|
if (!modalUsernameField?.value || !modalPasswordField?.value) {
|
489
615
|
alert('Please enter both username and password');
|
490
616
|
return;
|
491
617
|
}
|
492
|
-
|
618
|
+
|
493
619
|
if (!this.currentCommandData) {
|
494
620
|
alert('No command selected');
|
495
621
|
return;
|
496
622
|
}
|
497
|
-
|
623
|
+
|
498
624
|
console.log('Executing command:', this.currentCommandData);
|
499
|
-
|
625
|
+
|
500
626
|
// Set processing flag and update UI
|
501
627
|
this.setCommandProcessing(true);
|
502
|
-
|
628
|
+
|
503
629
|
// Update output area
|
504
630
|
this.showCommandRunning();
|
505
|
-
|
631
|
+
|
506
632
|
// Prepare and submit form
|
507
633
|
this.submitCommandForm();
|
508
634
|
},
|
509
|
-
|
635
|
+
|
510
636
|
/**
|
511
637
|
* Set command processing state
|
512
638
|
*/
|
513
|
-
setCommandProcessing: function(processing) {
|
639
|
+
setCommandProcessing: function (processing) {
|
514
640
|
if (!this.currentCommandData) return;
|
515
|
-
|
641
|
+
|
516
642
|
const button = this.currentCommandData.button;
|
517
643
|
const buttonIcon = button.querySelector('i');
|
518
|
-
|
644
|
+
|
519
645
|
button.dataset.processing = processing.toString();
|
520
646
|
button.disabled = processing;
|
521
|
-
|
647
|
+
|
522
648
|
if (processing) {
|
523
649
|
buttonIcon.className = 'mdi mdi-loading mdi-spin';
|
524
650
|
} else {
|
525
651
|
buttonIcon.className = this.currentCommandData.originalIconClass;
|
526
652
|
}
|
527
653
|
},
|
528
|
-
|
654
|
+
|
529
655
|
/**
|
530
656
|
* Show command running state in output area
|
531
657
|
*/
|
532
|
-
showCommandRunning: function() {
|
658
|
+
showCommandRunning: function () {
|
533
659
|
const outputContainer = document.getElementById('commandOutputContainer');
|
534
660
|
const commandHeader = document.querySelector('.output-card .card-header span.text-muted');
|
535
|
-
|
661
|
+
|
536
662
|
if (outputContainer) {
|
537
663
|
outputContainer.innerHTML = `
|
538
664
|
<div class="alert alert-primary d-flex align-items-center mb-0">
|
@@ -544,44 +670,44 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
544
670
|
</div>
|
545
671
|
`;
|
546
672
|
}
|
547
|
-
|
673
|
+
|
548
674
|
// Update command header to show executing command
|
549
675
|
if (commandHeader) {
|
550
676
|
commandHeader.textContent = `Executing: ${this.currentCommandData.name}`;
|
551
677
|
}
|
552
678
|
},
|
553
|
-
|
679
|
+
|
554
680
|
/**
|
555
681
|
* Submit the command execution form
|
556
682
|
*/
|
557
|
-
submitCommandForm: function() {
|
683
|
+
submitCommandForm: function () {
|
558
684
|
const commandForm = document.getElementById('commandExecutionForm');
|
559
685
|
const selectedCommandIdField = document.getElementById('selectedCommandId');
|
560
686
|
const formUsernameField = document.getElementById('formUsername');
|
561
687
|
const formPasswordField = document.getElementById('formPassword');
|
562
688
|
const modalUsernameField = document.getElementById('modalUsername');
|
563
689
|
const modalPasswordField = document.getElementById('modalPassword');
|
564
|
-
|
690
|
+
|
565
691
|
if (!commandForm) {
|
566
692
|
console.error('Command form not found');
|
567
693
|
this.handleSubmissionError('Form not found');
|
568
694
|
return;
|
569
695
|
}
|
570
|
-
|
696
|
+
|
571
697
|
// Prepare form data
|
572
698
|
if (selectedCommandIdField) selectedCommandIdField.value = this.currentCommandData.id;
|
573
699
|
if (formUsernameField) formUsernameField.value = modalUsernameField.value;
|
574
700
|
if (formPasswordField) formPasswordField.value = modalPasswordField.value;
|
575
|
-
|
701
|
+
|
576
702
|
console.log('Form data:', {
|
577
703
|
commandId: selectedCommandIdField?.value,
|
578
704
|
username: formUsernameField?.value,
|
579
705
|
hasPassword: !!formPasswordField?.value
|
580
706
|
});
|
581
|
-
|
707
|
+
|
582
708
|
// Close modal
|
583
709
|
Toolkit.ModalManager.hide();
|
584
|
-
|
710
|
+
|
585
711
|
try {
|
586
712
|
commandForm.submit();
|
587
713
|
} catch (error) {
|
@@ -589,20 +715,20 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
589
715
|
this.handleSubmissionError('Error submitting form. Please check the console for details.');
|
590
716
|
}
|
591
717
|
},
|
592
|
-
|
718
|
+
|
593
719
|
/**
|
594
720
|
* Handle form submission errors
|
595
721
|
*/
|
596
|
-
handleSubmissionError: function(message) {
|
722
|
+
handleSubmissionError: function (message) {
|
597
723
|
alert(message);
|
598
|
-
|
724
|
+
|
599
725
|
// Reset processing state
|
600
726
|
this.setCommandProcessing(false);
|
601
|
-
|
727
|
+
|
602
728
|
// Show error in output container
|
603
729
|
const outputContainer = document.getElementById('commandOutputContainer');
|
604
730
|
const commandHeader = document.querySelector('.output-card .card-header span.text-muted');
|
605
|
-
|
731
|
+
|
606
732
|
if (outputContainer) {
|
607
733
|
outputContainer.innerHTML = `
|
608
734
|
<div class="alert alert-danger d-flex align-items-start mb-3">
|
@@ -617,41 +743,41 @@ window.NetBoxToolkit = window.NetBoxToolkit || {};
|
|
617
743
|
</div>
|
618
744
|
`;
|
619
745
|
}
|
620
|
-
|
746
|
+
|
621
747
|
// Clear command header
|
622
748
|
if (commandHeader) {
|
623
749
|
commandHeader.textContent = '';
|
624
750
|
}
|
625
751
|
}
|
626
752
|
};
|
627
|
-
|
753
|
+
|
628
754
|
/**
|
629
755
|
* Main initialization function
|
630
756
|
*/
|
631
|
-
Toolkit.init = function() {
|
757
|
+
Toolkit.init = function () {
|
632
758
|
console.log('Initializing NetBox Toolkit JavaScript');
|
633
|
-
|
759
|
+
|
634
760
|
// Initialize copy functionality (available on all pages)
|
635
761
|
this.CopyManager.init();
|
636
|
-
|
762
|
+
|
637
763
|
// Initialize command functionality only if elements exist (device toolkit page)
|
638
764
|
const commandForm = document.getElementById('commandExecutionForm');
|
639
765
|
if (commandForm) {
|
640
766
|
this.CommandManager.init();
|
641
767
|
}
|
642
|
-
|
768
|
+
|
643
769
|
this.initialized = true;
|
644
770
|
console.log('NetBox Toolkit JavaScript initialized successfully');
|
645
771
|
};
|
646
|
-
|
772
|
+
|
647
773
|
// Auto-initialize when DOM is ready
|
648
774
|
if (document.readyState === 'loading') {
|
649
|
-
document.addEventListener('DOMContentLoaded', function() {
|
775
|
+
document.addEventListener('DOMContentLoaded', function () {
|
650
776
|
Toolkit.init();
|
651
777
|
});
|
652
778
|
} else {
|
653
779
|
// DOM is already ready
|
654
780
|
Toolkit.init();
|
655
781
|
}
|
656
|
-
|
782
|
+
|
657
783
|
})(window.NetBoxToolkit);
|