From a7d23846e60eb96132e2d1fbcbb295e28a640183 Mon Sep 17 00:00:00 2001 From: Micky <60691199+AliMickey@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:59:19 +1100 Subject: [PATCH] more cleanup --- app/functions/utils.py | 63 ++++++-------------- app/static/js/main.js | 127 +++++++++++++++++----------------------- app/templates/base.html | 7 --- app/views/main.py | 44 ++------------ 4 files changed, 77 insertions(+), 164 deletions(-) diff --git a/app/functions/utils.py b/app/functions/utils.py index 6cefd89..88ac041 100644 --- a/app/functions/utils.py +++ b/app/functions/utils.py @@ -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'} diff --git a/app/static/js/main.js b/app/static/js/main.js index 084025c..687fa40 100644 --- a/app/static/js/main.js +++ b/app/static/js/main.js @@ -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); } } }); diff --git a/app/templates/base.html b/app/templates/base.html index bd377dc..775bb7b 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -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'); - } diff --git a/app/views/main.py b/app/views/main.py index c5c9398..3aed343 100644 --- a/app/views/main.py +++ b/app/views/main.py @@ -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