more cleanup
This commit is contained in:
parent
99fba8bd1e
commit
a7d23846e6
@ -1,6 +1,4 @@
|
||||
import yaml
|
||||
import os
|
||||
import logging
|
||||
import os, yaml, logging
|
||||
from functools import wraps
|
||||
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
|
||||
|
||||
@ -54,21 +52,20 @@ def execute_command(device, command_format, target, ip_version):
|
||||
device_config = {
|
||||
'device_type': device['type'],
|
||||
'host': device['host'],
|
||||
'port': device['port'],
|
||||
'username': device['username'],
|
||||
'password': device['password'],
|
||||
'port': device.get('port', 22),
|
||||
'timeout': device.get('timeout', 10),
|
||||
'session_timeout': device.get('session_timeout', 60),
|
||||
'conn_timeout': device.get('conn_timeout', 10),
|
||||
'auth_timeout': device.get('auth_timeout', 10),
|
||||
'timeout': 10,
|
||||
'session_timeout': 60,
|
||||
'conn_timeout': 10,
|
||||
'auth_timeout': 10,
|
||||
}
|
||||
|
||||
command_timeout = device.get('command_timeout', 30)
|
||||
command_timeout = 30
|
||||
|
||||
try:
|
||||
with establish_connection(device_config) as connection:
|
||||
# Format the command
|
||||
print(command_format)
|
||||
command = str(command_format.format(ip_version=ip_version, target=target).strip())
|
||||
|
||||
# Execute the command
|
||||
@ -82,45 +79,21 @@ def execute_command(device, command_format, target, ip_version):
|
||||
|
||||
# Clean output
|
||||
output = output.strip() if output else ""
|
||||
logger.debug(f"Command output: {output}")
|
||||
|
||||
if not output:
|
||||
return {
|
||||
'raw_output': "No output received from device",
|
||||
'structured_data': None,
|
||||
'error': True,
|
||||
'error_type': 'no_output'
|
||||
}
|
||||
logger.error(f"No response from {device['host']}")
|
||||
return {'error': True, 'message': 'No response from device'}
|
||||
|
||||
return {
|
||||
'raw_output': output,
|
||||
'error': False
|
||||
}
|
||||
return {'error': False, 'message': output}
|
||||
|
||||
except NetmikoTimeoutException as e:
|
||||
error_msg = f"Timeout error on {device['host']}: {e}"
|
||||
logger.error(error_msg)
|
||||
return {
|
||||
'raw_output': error_msg,
|
||||
'structured_data': None,
|
||||
'error': True,
|
||||
'error_type': 'timeout'
|
||||
}
|
||||
logger.error(f"Timeout error on {device['host']}: {e}")
|
||||
return {'error': True, 'message': 'Timeout error'}
|
||||
|
||||
except NetmikoAuthenticationException as e:
|
||||
error_msg = f"Authentication failed for {device['host']}: {e}"
|
||||
logger.error(error_msg)
|
||||
return {
|
||||
'raw_output': error_msg,
|
||||
'structured_data': None,
|
||||
'error': True,
|
||||
'error_type': 'auth'
|
||||
}
|
||||
logger.error(f"Authentication failed for {device['host']}: {e}")
|
||||
return {'error': True, 'message': 'Authentication failed'}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"An unexpected error occurred on {device['host']}: {e}"
|
||||
logger.error(error_msg)
|
||||
return {
|
||||
'raw_output': error_msg,
|
||||
'structured_data': None,
|
||||
'error': True,
|
||||
'error_type': 'connection'
|
||||
}
|
||||
logger.error(f"An unexpected error occurred on {device['host']}: {e}")
|
||||
return {'error': True, 'message': 'Unexpected error'}
|
||||
|
@ -8,21 +8,20 @@ const app = Vue.createApp({
|
||||
selectedIpVersion: 'IPv4',
|
||||
isLoading: false,
|
||||
commandResult: '',
|
||||
devices: window.initialData?.devices || [],
|
||||
commands: window.initialData?.commands || [],
|
||||
devices: window.initialData?.devices ?? [],
|
||||
commands: window.initialData?.commands ?? [],
|
||||
currentCommand: null,
|
||||
showHelp: false,
|
||||
showTerms: false,
|
||||
isOpen: false,
|
||||
isDark: localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches),
|
||||
site_config: window.initialData?.site_config || {},
|
||||
peeringdbUrl: window.initialData?.site_config?.footer?.external_links?.peeringdb || '',
|
||||
githubUrl: window.initialData?.site_config?.footer?.external_links?.github || ''
|
||||
isDark: this.getInitialTheme(),
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateThemeClass();
|
||||
},
|
||||
|
||||
watch: {
|
||||
selectedCommand: {
|
||||
handler(newVal) {
|
||||
@ -31,74 +30,78 @@ const app = Vue.createApp({
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
showIpVersionSelector() {
|
||||
if (!this.targetIp) return true;
|
||||
|
||||
// Check if the input is not a valid IP address
|
||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){0,7}:([0-9a-fA-F]{1,4}:){0,7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
const ipValidation = {
|
||||
v4: /^(\d{1,3}\.){3}\d{1,3}$/,
|
||||
v6: /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){0,7}:([0-9a-fA-F]{1,4}:){0,7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
|
||||
};
|
||||
|
||||
// Detect IP version
|
||||
if (ipv6Regex.test(this.targetIp)) {
|
||||
if (ipValidation.v6.test(this.targetIp)) {
|
||||
this.selectedIpVersion = 'IPv6';
|
||||
return false; // Disable button for valid IPv6
|
||||
} else if (ipv4Regex.test(this.targetIp)) {
|
||||
this.selectedIpVersion = 'IPv4';
|
||||
return false; // Disable button for valid IPv4
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // Enable button for non-IP input
|
||||
if (ipValidation.v4.test(this.targetIp)) {
|
||||
this.selectedIpVersion = 'IPv4';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isValidInput() {
|
||||
if (!this.currentCommand || !this.targetIp) return false;
|
||||
|
||||
// If IP version selector is shown, any input is valid as it will be resolved
|
||||
if (this.showIpVersionSelector) return true;
|
||||
|
||||
if (this.currentCommand.field?.validation) {
|
||||
const pattern = new RegExp(this.currentCommand.field.validation);
|
||||
return pattern.test(this.targetIp);
|
||||
}
|
||||
return true;
|
||||
const { validation } = this.currentCommand.field ?? {};
|
||||
return validation ? new RegExp(validation).test(this.targetIp) : true;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getInitialTheme() {
|
||||
return localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
},
|
||||
|
||||
toggleIpVersion() {
|
||||
this.selectedIpVersion = this.selectedIpVersion === 'IPv4' ? 'IPv6' : 'IPv4';
|
||||
},
|
||||
|
||||
toggleTheme() {
|
||||
this.isDark = !this.isDark;
|
||||
localStorage.theme = this.isDark ? 'dark' : 'light';
|
||||
this.updateThemeClass();
|
||||
},
|
||||
|
||||
updateThemeClass() {
|
||||
if (this.isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
document.documentElement.classList[this.isDark ? 'add' : 'remove']('dark');
|
||||
},
|
||||
|
||||
toggleDevice(device) {
|
||||
// Update the selected device state
|
||||
const wasDeselected = this.selectedDevice === device;
|
||||
this.selectedDevice = wasDeselected ? '' : device;
|
||||
|
||||
// Only reset states if we're deselecting the device entirely
|
||||
if (wasDeselected) {
|
||||
this.selectedCommand = '';
|
||||
this.targetIp = '';
|
||||
this.commandResult = '';
|
||||
this.resetCommandState();
|
||||
}
|
||||
|
||||
// Force a re-render to ensure styling is updated
|
||||
this.$nextTick(() => {
|
||||
this.$forceUpdate();
|
||||
});
|
||||
this.$nextTick(() => this.$forceUpdate());
|
||||
},
|
||||
|
||||
resetCommandState() {
|
||||
this.selectedCommand = '';
|
||||
this.targetIp = '';
|
||||
this.commandResult = '';
|
||||
},
|
||||
|
||||
async executeCommand() {
|
||||
if (!this.isValidInput) {
|
||||
this.commandResult = '❌ Error: Please enter a valid input according to the command requirements';
|
||||
this.commandResult = '❌ Error: Please enter a valid input.';
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,9 +111,7 @@ const app = Vue.createApp({
|
||||
try {
|
||||
const response = await fetch('/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
device: this.selectedDevice,
|
||||
command: this.selectedCommand,
|
||||
@ -121,50 +122,32 @@ const app = Vue.createApp({
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
this.commandResult = `❌ Error: ${data.message || 'An unknown error occurred'}`;
|
||||
if (!response.ok || data.error) {
|
||||
this.commandResult = `Error: ${data.message || 'An error occurred.'}`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
// Handle error responses with specific error types
|
||||
const errorPrefix = data.error_type === 'timeout' ? '🕒 Timeout Error: ' :
|
||||
data.error_type === 'auth' ? '🔒 Authentication Error: ' :
|
||||
data.error_type === 'connection' ? '🔌 Connection Error: ' :
|
||||
data.error_type === 'no_output' ? '📭 No Output Error: ' :
|
||||
'❌ Error: ';
|
||||
this.commandResult = errorPrefix + data.message;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle successful response
|
||||
if (data.result) {
|
||||
this.commandResult = data.result;
|
||||
} else {
|
||||
this.commandResult = '❌ Error: No output received from command';
|
||||
}
|
||||
this.commandResult = data.message || 'Error: No output received from command.';
|
||||
} catch (error) {
|
||||
console.error('Command execution error:', error);
|
||||
this.commandResult = '❌ Error: Failed to execute command. Please try again.';
|
||||
this.commandResult = 'Error: An error occurred.';
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
toggleDropdown(event) {
|
||||
this.isOpen = !this.isOpen;
|
||||
},
|
||||
|
||||
closeDropdown(event) {
|
||||
// Only close if clicking outside the dropdown
|
||||
if (!event.target.closest('.relative')) {
|
||||
this.isOpen = false;
|
||||
}
|
||||
},
|
||||
|
||||
selectCommand(command) {
|
||||
this.selectedCommand = command;
|
||||
// Add a small delay before closing to ensure the selection is visible
|
||||
setTimeout(() => {
|
||||
this.isOpen = false;
|
||||
}, 100);
|
||||
setTimeout(() => this.isOpen = false, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -16,13 +16,6 @@
|
||||
commands: {{ commands|tojson|safe }},
|
||||
devices: {{ devices|tojson|safe }}
|
||||
};
|
||||
|
||||
// Check for saved theme preference or default to 'light'
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from flask import (
|
||||
Blueprint, request, jsonify, render_template
|
||||
Blueprint, request, render_template
|
||||
)
|
||||
import logging
|
||||
|
||||
@ -56,43 +56,7 @@ def execute():
|
||||
|
||||
ip_version = 6 if ip_version == "IPv6" else 4
|
||||
|
||||
try:
|
||||
# Execute the command using network_utils
|
||||
result = execute_command(device, command['format'], target, ip_version)
|
||||
# Execute the command using network_utils
|
||||
result = execute_command(device, command['format'], target, ip_version)
|
||||
|
||||
if not result:
|
||||
error_msg = 'No response from command execution'
|
||||
logger.error(error_msg)
|
||||
return jsonify({
|
||||
'error': True,
|
||||
'message': error_msg,
|
||||
'error_type': 'no_response'
|
||||
})
|
||||
|
||||
# Check for error state
|
||||
if result.get('error', False):
|
||||
error_msg = result['raw_output']
|
||||
logger.error(f"Command execution failed: {error_msg}")
|
||||
return jsonify({
|
||||
'error': True,
|
||||
'message': error_msg,
|
||||
'error_type': result.get('error_type', 'general')
|
||||
})
|
||||
|
||||
# Log successful execution
|
||||
logger.info(f"Successfully executed command {command} on {device}")
|
||||
|
||||
# Return successful result
|
||||
return jsonify({
|
||||
'error': False,
|
||||
'result': result['raw_output'],
|
||||
'structured_data': result.get('structured_data')
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during command execution: {str(e)}")
|
||||
return jsonify({
|
||||
'error': True,
|
||||
'message': f"An unexpected error occurred: {str(e)}",
|
||||
'error_type': 'general'
|
||||
})
|
||||
return result
|
||||
|
Loading…
x
Reference in New Issue
Block a user