more cleanup

This commit is contained in:
Micky 2024-12-26 11:59:19 +11:00
parent 99fba8bd1e
commit a7d23846e6
4 changed files with 77 additions and 164 deletions

View File

@ -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'}

View File

@ -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,86 +30,88 @@ 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)) {
return false;
}
if (ipValidation.v4.test(this.targetIp)) {
this.selectedIpVersion = 'IPv4';
return false; // Disable button for valid IPv4
return false;
}
return true; // Enable button for non-IP input
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;
}
this.isLoading = true;
this.commandResult = '';
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,
@ -118,53 +119,35 @@ const app = Vue.createApp({
ipVersion: this.selectedIpVersion
})
});
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);
}
}
});

View File

@ -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>

View File

@ -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