more cleanup
This commit is contained in:
parent
99fba8bd1e
commit
a7d23846e6
@ -1,6 +1,4 @@
|
|||||||
import yaml
|
import os, yaml, logging
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
|
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
|
||||||
|
|
||||||
@ -54,21 +52,20 @@ def execute_command(device, command_format, target, ip_version):
|
|||||||
device_config = {
|
device_config = {
|
||||||
'device_type': device['type'],
|
'device_type': device['type'],
|
||||||
'host': device['host'],
|
'host': device['host'],
|
||||||
|
'port': device['port'],
|
||||||
'username': device['username'],
|
'username': device['username'],
|
||||||
'password': device['password'],
|
'password': device['password'],
|
||||||
'port': device.get('port', 22),
|
'timeout': 10,
|
||||||
'timeout': device.get('timeout', 10),
|
'session_timeout': 60,
|
||||||
'session_timeout': device.get('session_timeout', 60),
|
'conn_timeout': 10,
|
||||||
'conn_timeout': device.get('conn_timeout', 10),
|
'auth_timeout': 10,
|
||||||
'auth_timeout': device.get('auth_timeout', 10),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command_timeout = device.get('command_timeout', 30)
|
command_timeout = 30
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with establish_connection(device_config) as connection:
|
with establish_connection(device_config) as connection:
|
||||||
# Format the command
|
# Format the command
|
||||||
print(command_format)
|
|
||||||
command = str(command_format.format(ip_version=ip_version, target=target).strip())
|
command = str(command_format.format(ip_version=ip_version, target=target).strip())
|
||||||
|
|
||||||
# Execute the command
|
# Execute the command
|
||||||
@ -82,45 +79,21 @@ def execute_command(device, command_format, target, ip_version):
|
|||||||
|
|
||||||
# Clean output
|
# Clean output
|
||||||
output = output.strip() if output else ""
|
output = output.strip() if output else ""
|
||||||
logger.debug(f"Command output: {output}")
|
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
return {
|
logger.error(f"No response from {device['host']}")
|
||||||
'raw_output': "No output received from device",
|
return {'error': True, 'message': 'No response from device'}
|
||||||
'structured_data': None,
|
|
||||||
'error': True,
|
|
||||||
'error_type': 'no_output'
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {'error': False, 'message': output}
|
||||||
'raw_output': output,
|
|
||||||
'error': False
|
|
||||||
}
|
|
||||||
|
|
||||||
except NetmikoTimeoutException as e:
|
except NetmikoTimeoutException as e:
|
||||||
error_msg = f"Timeout error on {device['host']}: {e}"
|
logger.error(f"Timeout error on {device['host']}: {e}")
|
||||||
logger.error(error_msg)
|
return {'error': True, 'message': 'Timeout error'}
|
||||||
return {
|
|
||||||
'raw_output': error_msg,
|
|
||||||
'structured_data': None,
|
|
||||||
'error': True,
|
|
||||||
'error_type': 'timeout'
|
|
||||||
}
|
|
||||||
except NetmikoAuthenticationException as e:
|
except NetmikoAuthenticationException as e:
|
||||||
error_msg = f"Authentication failed for {device['host']}: {e}"
|
logger.error(f"Authentication failed for {device['host']}: {e}")
|
||||||
logger.error(error_msg)
|
return {'error': True, 'message': 'Authentication failed'}
|
||||||
return {
|
|
||||||
'raw_output': error_msg,
|
|
||||||
'structured_data': None,
|
|
||||||
'error': True,
|
|
||||||
'error_type': 'auth'
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"An unexpected error occurred on {device['host']}: {e}"
|
logger.error(f"An unexpected error occurred on {device['host']}: {e}")
|
||||||
logger.error(error_msg)
|
return {'error': True, 'message': 'Unexpected error'}
|
||||||
return {
|
|
||||||
'raw_output': error_msg,
|
|
||||||
'structured_data': None,
|
|
||||||
'error': True,
|
|
||||||
'error_type': 'connection'
|
|
||||||
}
|
|
||||||
|
@ -8,21 +8,20 @@ const app = Vue.createApp({
|
|||||||
selectedIpVersion: 'IPv4',
|
selectedIpVersion: 'IPv4',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
commandResult: '',
|
commandResult: '',
|
||||||
devices: window.initialData?.devices || [],
|
devices: window.initialData?.devices ?? [],
|
||||||
commands: window.initialData?.commands || [],
|
commands: window.initialData?.commands ?? [],
|
||||||
currentCommand: null,
|
currentCommand: null,
|
||||||
showHelp: false,
|
showHelp: false,
|
||||||
showTerms: false,
|
showTerms: false,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
isDark: localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches),
|
isDark: this.getInitialTheme(),
|
||||||
site_config: window.initialData?.site_config || {},
|
|
||||||
peeringdbUrl: window.initialData?.site_config?.footer?.external_links?.peeringdb || '',
|
|
||||||
githubUrl: window.initialData?.site_config?.footer?.external_links?.github || ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateThemeClass();
|
this.updateThemeClass();
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
selectedCommand: {
|
selectedCommand: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
@ -31,86 +30,88 @@ const app = Vue.createApp({
|
|||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
showIpVersionSelector() {
|
showIpVersionSelector() {
|
||||||
if (!this.targetIp) return true;
|
if (!this.targetIp) return true;
|
||||||
|
|
||||||
// Check if the input is not a valid IP address
|
const ipValidation = {
|
||||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
v4: /^(\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}$/;
|
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 (ipValidation.v6.test(this.targetIp)) {
|
||||||
if (ipv6Regex.test(this.targetIp)) {
|
|
||||||
this.selectedIpVersion = 'IPv6';
|
this.selectedIpVersion = 'IPv6';
|
||||||
return false; // Disable button for valid IPv6
|
return false;
|
||||||
} else if (ipv4Regex.test(this.targetIp)) {
|
}
|
||||||
|
|
||||||
|
if (ipValidation.v4.test(this.targetIp)) {
|
||||||
this.selectedIpVersion = 'IPv4';
|
this.selectedIpVersion = 'IPv4';
|
||||||
return false; // Disable button for valid IPv4
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // Enable button for non-IP input
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
isValidInput() {
|
isValidInput() {
|
||||||
if (!this.currentCommand || !this.targetIp) return false;
|
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.showIpVersionSelector) return true;
|
||||||
|
|
||||||
if (this.currentCommand.field?.validation) {
|
const { validation } = this.currentCommand.field ?? {};
|
||||||
const pattern = new RegExp(this.currentCommand.field.validation);
|
return validation ? new RegExp(validation).test(this.targetIp) : true;
|
||||||
return pattern.test(this.targetIp);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
getInitialTheme() {
|
||||||
|
return localStorage.theme === 'dark' ||
|
||||||
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
},
|
||||||
|
|
||||||
toggleIpVersion() {
|
toggleIpVersion() {
|
||||||
this.selectedIpVersion = this.selectedIpVersion === 'IPv4' ? 'IPv6' : 'IPv4';
|
this.selectedIpVersion = this.selectedIpVersion === 'IPv4' ? 'IPv6' : 'IPv4';
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDark = !this.isDark;
|
this.isDark = !this.isDark;
|
||||||
localStorage.theme = this.isDark ? 'dark' : 'light';
|
localStorage.theme = this.isDark ? 'dark' : 'light';
|
||||||
this.updateThemeClass();
|
this.updateThemeClass();
|
||||||
},
|
},
|
||||||
|
|
||||||
updateThemeClass() {
|
updateThemeClass() {
|
||||||
if (this.isDark) {
|
document.documentElement.classList[this.isDark ? 'add' : 'remove']('dark');
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleDevice(device) {
|
toggleDevice(device) {
|
||||||
// Update the selected device state
|
|
||||||
const wasDeselected = this.selectedDevice === device;
|
const wasDeselected = this.selectedDevice === device;
|
||||||
this.selectedDevice = wasDeselected ? '' : device;
|
this.selectedDevice = wasDeselected ? '' : device;
|
||||||
|
|
||||||
// Only reset states if we're deselecting the device entirely
|
|
||||||
if (wasDeselected) {
|
if (wasDeselected) {
|
||||||
this.selectedCommand = '';
|
this.resetCommandState();
|
||||||
this.targetIp = '';
|
|
||||||
this.commandResult = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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() {
|
async executeCommand() {
|
||||||
if (!this.isValidInput) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.commandResult = '';
|
this.commandResult = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/execute', {
|
const response = await fetch('/execute', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
device: this.selectedDevice,
|
device: this.selectedDevice,
|
||||||
command: this.selectedCommand,
|
command: this.selectedCommand,
|
||||||
@ -118,53 +119,35 @@ const app = Vue.createApp({
|
|||||||
ipVersion: this.selectedIpVersion
|
ipVersion: this.selectedIpVersion
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok || data.error) {
|
||||||
this.commandResult = `❌ Error: ${data.message || 'An unknown error occurred'}`;
|
this.commandResult = `Error: ${data.message || 'An error occurred.'}`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.error) {
|
this.commandResult = data.message || 'Error: No output received from command.';
|
||||||
// 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';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Command execution error:', error);
|
this.commandResult = 'Error: An error occurred.';
|
||||||
this.commandResult = '❌ Error: Failed to execute command. Please try again.';
|
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleDropdown(event) {
|
toggleDropdown(event) {
|
||||||
this.isOpen = !this.isOpen;
|
this.isOpen = !this.isOpen;
|
||||||
},
|
},
|
||||||
|
|
||||||
closeDropdown(event) {
|
closeDropdown(event) {
|
||||||
// Only close if clicking outside the dropdown
|
|
||||||
if (!event.target.closest('.relative')) {
|
if (!event.target.closest('.relative')) {
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectCommand(command) {
|
selectCommand(command) {
|
||||||
this.selectedCommand = 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 }},
|
commands: {{ commands|tojson|safe }},
|
||||||
devices: {{ devices|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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, request, jsonify, render_template
|
Blueprint, request, render_template
|
||||||
)
|
)
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -56,43 +56,7 @@ def execute():
|
|||||||
|
|
||||||
ip_version = 6 if ip_version == "IPv6" else 4
|
ip_version = 6 if ip_version == "IPv6" else 4
|
||||||
|
|
||||||
try:
|
# Execute the command using network_utils
|
||||||
# Execute the command using network_utils
|
result = execute_command(device, command['format'], target, ip_version)
|
||||||
result = execute_command(device, command['format'], target, ip_version)
|
|
||||||
|
|
||||||
if not result:
|
return 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'
|
|
||||||
})
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user