Updated IP configuration and history; fixed previous IP address and added subnet mask to history entries. Enhanced UI with context menu and subnet mask application functionality.
This commit is contained in:
parent
5fea463343
commit
5a0a0a1407
|
@ -1 +1 @@
|
|||
{"last_interface": "Ethernet", "last_ip_prefix": "10.1.33", "previous_config": {"name": "Ethernet", "ip_address": "192.168.88.249", "subnet_mask": "255.255.255.0", "gateway": "192.168.88.1", "dhcp_enabled": false}, "last_subnet_mask": "255.240.0.0"}
|
||||
{"last_interface": "Ethernet", "last_ip_prefix": "10.1.33", "previous_config": {"name": "Ethernet", "ip_address": "10.1.33.249", "subnet_mask": "255.255.255.0", "gateway": "10.1.33.1", "dhcp_enabled": false}, "last_subnet_mask": "255.240.0.0"}
|
|
@ -1 +1 @@
|
|||
[{"prefix": "10.1.33", "mask": "255.240.0.0"}, {"prefix": "192.168.88", "mask": "255.255.255.0"}, {"prefix": "192.168.199", "mask": "255.255.255.0"}, {"prefix": "192.168.200", "mask": "255.255.255.0"}, {"prefix": "10.202.4", "mask": "255.255.0.0"}, {"prefix": "169.254.38", "mask": "255.255.0.0"}, {"prefix": "192.168.1", "mask": "255.255.255.0"}, {"prefix": "169.254.69", "mask": "255.255.0.0"}, {"prefix": "192.168.212", "mask": "255.255.255.0"}, {"prefix": "10.1.20", "mask": "255.240.0.0"}, {"prefix": "10.1.22", "mask": "255.255.255.0"}, {"prefix": "10.146.76", "mask": "255.255.255.0"}, {"prefix": "192.168.0", "mask": "255.255.255.0"}, {"prefix": "10.101.8", "mask": "255.255.255.0"}, {"prefix": "10.1.92", "mask": "255.255.255.0"}]
|
||||
[{"prefix": "10.1.33", "mask": "255.240.0.0"}, {"prefix": "169.254.38", "mask": "255.255.0.0"}, {"prefix": "192.168.1", "mask": "255.255.255.0"}, {"prefix": "192.168.212", "mask": "255.255.255.0"}, {"prefix": "192.168.193", "mask": "255.255.255.0"}, {"prefix": "192.168.88", "mask": "255.255.255.0"}, {"prefix": "192.168.199", "mask": "255.255.255.0"}, {"prefix": "192.168.200", "mask": "255.255.255.0"}, {"prefix": "10.202.4", "mask": "255.255.0.0"}, {"prefix": "169.254.69", "mask": "255.255.0.0"}, {"prefix": "10.1.20", "mask": "255.240.0.0"}, {"prefix": "10.1.22", "mask": "255.255.255.0"}, {"prefix": "10.146.76", "mask": "255.255.255.0"}, {"prefix": "192.168.0", "mask": "255.255.255.0"}, {"prefix": "10.101.8", "mask": "255.255.255.0"}]
|
|
@ -224,6 +224,12 @@ class IPChangerApp:
|
|||
if MAC_LOOKUP_AVAILABLE:
|
||||
self.mac_lookup = MacLookup()
|
||||
|
||||
# Crear menú contextual
|
||||
self.create_context_menu()
|
||||
|
||||
# Manejar el cierre de la ventana
|
||||
self.master.protocol("WM_DELETE_WINDOW", self.on_app_exit)
|
||||
|
||||
def setup_mask_traces(self):
|
||||
"""Set up traces for subnet mask and CIDR fields safely"""
|
||||
# First initialize variables
|
||||
|
@ -476,6 +482,10 @@ class IPChangerApp:
|
|||
command=lambda: self.set_cidr_preset(24),
|
||||
).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# Botón para aplicar la máscara de subred
|
||||
ttk.Button(subnet_frame, text="Apply Mask", command=self.apply_subnet_mask).pack(
|
||||
side=tk.LEFT, padx=5
|
||||
)
|
||||
# Sección de historial
|
||||
self.history_frame = ttk.LabelFrame(
|
||||
self.control_frame, text="IP History", padding="5"
|
||||
|
@ -816,12 +826,17 @@ class IPChangerApp:
|
|||
self.save_ip_history()
|
||||
self.update_history_display()
|
||||
|
||||
# Save the last used subnet mask
|
||||
self.config.last_subnet_mask = subnet_mask
|
||||
self.config.save_config()
|
||||
# Ensure the mask_to_use is valid before saving to config
|
||||
if self.is_valid_subnet_mask(subnet_mask):
|
||||
self.config.last_ip_prefix = ip_prefix
|
||||
self.config.last_subnet_mask = subnet_mask
|
||||
self.config.save_config()
|
||||
else:
|
||||
self.log_message(f"Attempted to save invalid mask '{subnet_mask}' to config for prefix '{ip_prefix}'. Skipped.", "WARN")
|
||||
|
||||
def update_history_display(self):
|
||||
# Create display values for combo box - show prefix and mask
|
||||
# Ensure ip_history is a list of dicts
|
||||
display_values = [
|
||||
f"{entry['prefix']} ({entry['mask']})" for entry in self.ip_history
|
||||
]
|
||||
|
@ -851,10 +866,11 @@ class IPChangerApp:
|
|||
if entry["prefix"] == ip_prefix:
|
||||
# Set the subnet mask from history
|
||||
self.subnet_mask.set(entry["mask"])
|
||||
# Move this entry to top of history (most recently used)
|
||||
# Move this entry to top of history and update config
|
||||
self.add_to_history(ip_prefix)
|
||||
break
|
||||
|
||||
# self.config.last_ip_prefix = ip_prefix # Moved to add_to_history
|
||||
# self.config.last_subnet_mask = self.subnet_mask.get() # Moved to add_to_history
|
||||
self.config.save_config()
|
||||
self.log_message(f"Selected IP prefix from history: {ip_prefix}")
|
||||
|
||||
|
@ -975,27 +991,44 @@ class IPChangerApp:
|
|||
"Enabled" if interface.dhcp_enabled else "Disabled"
|
||||
)
|
||||
|
||||
# Actualizar IP prefix si hay una IP válida
|
||||
ip_prefix_to_set = ""
|
||||
subnet_mask_to_set = self.config.last_subnet_mask # Default to last known good mask
|
||||
source_log_msg = ""
|
||||
|
||||
if interface.ip_address:
|
||||
prefix = ".".join(interface.ip_address.split(".")[:3])
|
||||
self.ip_prefix.set(prefix)
|
||||
|
||||
# Set subnet mask from interface
|
||||
ip_prefix_to_set = ".".join(interface.ip_address.split(".")[:3])
|
||||
if interface.subnet_mask:
|
||||
self.subnet_mask.set(interface.subnet_mask)
|
||||
subnet_mask_to_set = interface.subnet_mask
|
||||
# If interface.subnet_mask is empty, subnet_mask_to_set remains self.config.last_subnet_mask
|
||||
source_log_msg = f"from interface {selected}"
|
||||
elif self.ip_history: # Interface has no IP, try to load from top of history
|
||||
top_history_entry = self.ip_history[0]
|
||||
ip_prefix_to_set = top_history_entry["prefix"]
|
||||
subnet_mask_to_set = top_history_entry["mask"]
|
||||
source_log_msg = "from IP history (top item)"
|
||||
else: # No interface IP, no history, use last saved config from startup
|
||||
ip_prefix_to_set = self.config.last_ip_prefix
|
||||
subnet_mask_to_set = self.config.last_subnet_mask # Already set as default
|
||||
source_log_msg = "from last saved configuration"
|
||||
|
||||
# Update ping target for the new IP prefix
|
||||
self.update_ping_target(prefix)
|
||||
self.ip_prefix.set(ip_prefix_to_set)
|
||||
self.subnet_mask.set(subnet_mask_to_set) # This will trigger its trace to update CIDR
|
||||
|
||||
if ip_prefix_to_set:
|
||||
self.update_ping_target(ip_prefix_to_set)
|
||||
|
||||
# Guardar interfaz seleccionada
|
||||
self.config.last_interface = selected
|
||||
self.config.save_config()
|
||||
|
||||
self.log_message(f"Selected interface: {selected}")
|
||||
self.log_message(f"Selected interface: {selected}. IP fields populated {source_log_msg}.")
|
||||
|
||||
# Update scan IP range based on current IP and subnet mask
|
||||
# This uses self.current_ip_var, which is set from interface.ip_address
|
||||
if interface.ip_address and interface.subnet_mask:
|
||||
self.update_scan_ip_range(interface.ip_address, interface.subnet_mask)
|
||||
# Note: on_subnet_mask_changed (triggered by self.subnet_mask.set)
|
||||
# will also attempt to update scan range if self.current_ip_var.get() is valid.
|
||||
|
||||
def validate_ip_input(self) -> tuple[bool, str]:
|
||||
try:
|
||||
|
@ -1004,8 +1037,10 @@ class IPChangerApp:
|
|||
subnet_mask = self.subnet_mask.get().strip()
|
||||
|
||||
# Validar formato del prefijo
|
||||
if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}", prefix):
|
||||
if not prefix or not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}$", prefix):
|
||||
return False, "IP prefix must be in format: xxx.xxx.xxx"
|
||||
if any(not (0 <= int(p) <= 255) for p in prefix.split(".")):
|
||||
return False, "Each octet in IP prefix must be between 0 and 255"
|
||||
|
||||
# Validar último octeto
|
||||
if not last_octet.isdigit() or not 0 <= int(last_octet) <= 255:
|
||||
|
@ -1046,17 +1081,22 @@ class IPChangerApp:
|
|||
self.log_message(error_msg)
|
||||
return False
|
||||
|
||||
# Construir la línea de argumentos - pass both IP and subnet as a single parameter
|
||||
if mode != "dhcp":
|
||||
# For static IP, create the netsh command with subnet mask
|
||||
# Construir la línea de argumentos para ShellExecuteW
|
||||
if mode.lower().startswith("netsh"):
|
||||
# Si 'mode' ya es un comando netsh completo (viene de apply_subnet_mask)
|
||||
# El script admin lo ejecutará directamente.
|
||||
args = f'"{script_path}" "{interface}" "{mode}"'
|
||||
elif mode.lower() == "dhcp":
|
||||
# Para DHCP, el script admin construirá el comando netsh apropiado.
|
||||
args = f'"{script_path}" "{interface}" "dhcp"'
|
||||
else:
|
||||
# Si 'mode' es una dirección IP (viene de set_static_ip o restore_previous)
|
||||
# El script admin construirá el comando netsh usando esta IP y la subnet_mask provista.
|
||||
gateway = (
|
||||
f"{self.get_ip_prefix(mode)}.1" # Use the first three octets of the IP
|
||||
)
|
||||
netsh_cmd = f'netsh interface ip set address "{interface}" static {mode} {subnet_mask} {gateway}'
|
||||
args = f'"{script_path}" "{interface}" "{netsh_cmd}"'
|
||||
else:
|
||||
# For DHCP, keep it simple
|
||||
args = f'"{script_path}" "{interface}" "dhcp"'
|
||||
# El script ip-changer-admin.py espera la IP y luego la máscara como argumentos separados si no es un comando netsh completo.
|
||||
args = f'"{script_path}" "{interface}" "{mode}" "{subnet_mask}"' # Pasamos IP y máscara
|
||||
|
||||
if debug:
|
||||
self.log_message("Debug Information:")
|
||||
|
@ -1161,13 +1201,19 @@ class IPChangerApp:
|
|||
self.show_error("Previous IP address not available")
|
||||
return
|
||||
|
||||
if self.execute_admin_script(interface_name, ip):
|
||||
# Asegurarse de pasar la máscara de subred correcta al restaurar
|
||||
prev_mask = prev_config.get("subnet_mask", "255.255.255.0") # Usar máscara guardada o default
|
||||
if self.execute_admin_script(interface_name, ip, subnet_mask=prev_mask):
|
||||
self.log_message(f"Successfully restored IP {ip} on {interface_name}")
|
||||
time.sleep(2)
|
||||
self.refresh_interfaces()
|
||||
else:
|
||||
self.show_error("Failed to restore static IP configuration")
|
||||
|
||||
def get_ip_prefix(self, ip: str) -> str:
|
||||
"""Extrae los primeros 3 octetos de una dirección IP"""
|
||||
return ".".join(ip.split(".")[:3]) if "." in ip else ip
|
||||
|
||||
def do_ping(self):
|
||||
"""Realiza un ping a la dirección especificada usando una biblioteca de Python"""
|
||||
target = self.ping_target.get().strip()
|
||||
|
@ -1682,41 +1728,81 @@ class IPChangerApp:
|
|||
def set_static_ip(self):
|
||||
interface = self.if_var.get()
|
||||
if not interface:
|
||||
self.show_error("Please select a network interface")
|
||||
self.show_error("Please select a network interface.")
|
||||
return
|
||||
|
||||
# Guardar configuración actual antes de cambiarla
|
||||
self.save_previous_config(interface)
|
||||
|
||||
# Validar IP
|
||||
is_valid, result = self.validate_ip_input()
|
||||
if not is_valid:
|
||||
self.show_error(result)
|
||||
# Obtener IP y máscara del historial seleccionado
|
||||
selected_history_value = self.history_var.get()
|
||||
if not selected_history_value:
|
||||
self.show_error("Please select an IP configuration from the 'Previous IPs' list or ensure the list is not empty.")
|
||||
return
|
||||
|
||||
ip = result
|
||||
subnet_mask = self.subnet_mask.get()
|
||||
try:
|
||||
# Parsear: "192.168.1 (255.255.255.0)"
|
||||
match = re.match(r"^(.*?) \((.*?)\)$", selected_history_value)
|
||||
if not match:
|
||||
self.show_error(f"Invalid format in 'Previous IPs' selection: {selected_history_value}")
|
||||
return
|
||||
|
||||
# Guardar IP actual en el historial con la máscara de subred
|
||||
self.add_to_history(self.ip_prefix.get())
|
||||
history_prefix = match.group(1).strip()
|
||||
history_mask = match.group(2).strip()
|
||||
|
||||
if not self.is_valid_subnet_mask(history_mask):
|
||||
self.show_error(f"Invalid subnet mask '{history_mask}' from history selection.")
|
||||
return
|
||||
|
||||
if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}$", history_prefix) or \
|
||||
any(not (0 <= int(p) <= 255) for p in history_prefix.split(".")):
|
||||
self.show_error(f"Invalid IP prefix '{history_prefix}' from history selection.")
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
self.show_error(f"Error parsing history selection: {str(e)}")
|
||||
return
|
||||
|
||||
# Obtener el último octeto del campo de entrada
|
||||
last_octet_str = self.last_octet.get().strip()
|
||||
if not last_octet_str.isdigit() or not (0 <= int(last_octet_str) <= 255):
|
||||
self.show_error("Last octet must be a number between 0 and 255.")
|
||||
return
|
||||
|
||||
full_ip = f"{history_prefix}.{last_octet_str}"
|
||||
|
||||
try:
|
||||
ipaddress.IPv4Address(full_ip) # Validar IP completa
|
||||
except ValueError:
|
||||
self.show_error(f"Constructed IP address '{full_ip}' is invalid.")
|
||||
return
|
||||
|
||||
# Poblar los campos de la UI para consistencia y para add_to_history
|
||||
self.ip_prefix.set(history_prefix)
|
||||
self.subnet_mask.set(history_mask) # Esto también actualizará el CIDR
|
||||
|
||||
# Guardar IP (prefijo y máscara) en el historial.
|
||||
# add_to_history usará self.ip_prefix.get() y self.subnet_mask.get()
|
||||
self.add_to_history(history_prefix)
|
||||
|
||||
# Log the actual command we'll be executing
|
||||
gateway = f"{self.ip_prefix.get()}.1"
|
||||
command = f'netsh interface ip set address "{interface}" static {ip} {subnet_mask} {gateway}'
|
||||
gateway = f"{history_prefix}.1" # Gateway por defecto
|
||||
command = f'netsh interface ip set address "{interface}" static {full_ip} {history_mask} {gateway}'
|
||||
self.log_message(f"Executing network command: {command}")
|
||||
|
||||
# Ejecutar script con privilegios - pass subnet mask
|
||||
if self.execute_admin_script(
|
||||
interface, ip, debug=True, subnet_mask=subnet_mask
|
||||
interface, full_ip, debug=True, subnet_mask=history_mask
|
||||
):
|
||||
time.sleep(2) # Esperar a que se apliquen los cambios
|
||||
self.refresh_interfaces()
|
||||
self.log_message(
|
||||
f"Successfully set static IP {ip} with mask {subnet_mask} on {interface}"
|
||||
f"Successfully set static IP {full_ip} with mask {history_mask} on {interface} using history."
|
||||
)
|
||||
else:
|
||||
self.show_error("Failed to set static IP. Check the log for details.")
|
||||
|
||||
|
||||
def set_dhcp(self):
|
||||
interface = self.if_var.get()
|
||||
if not interface:
|
||||
|
@ -2125,12 +2211,89 @@ class IPChangerApp:
|
|||
self.cidr_bits.set(str(cidr_bits))
|
||||
# The on_cidr_bits_changed trace will update the subnet mask automatically
|
||||
|
||||
def apply_subnet_mask(self):
|
||||
"""
|
||||
Explicitly applies the current subnet mask, triggering updates for CIDR
|
||||
and applying the mask to the selected interface, keeping its current IP and gateway.
|
||||
"""
|
||||
if_name = self.if_var.get()
|
||||
if not if_name:
|
||||
self.show_error("Please select a network interface.")
|
||||
return
|
||||
|
||||
current_ip = self.current_ip_var.get()
|
||||
if not current_ip:
|
||||
self.show_error("Selected interface has no current IP address to apply mask to.")
|
||||
return
|
||||
try:
|
||||
ipaddress.IPv4Address(current_ip) # Validate current IP
|
||||
except ValueError:
|
||||
self.show_error(f"Current IP address '{current_ip}' of the interface is invalid.")
|
||||
return
|
||||
|
||||
new_mask = self.subnet_mask.get().strip()
|
||||
if not self.is_valid_subnet_mask(new_mask):
|
||||
self.show_error(f"The entered subnet mask '{new_mask}' is invalid.")
|
||||
return
|
||||
|
||||
current_gateway = self.current_gateway_var.get().strip()
|
||||
if not current_gateway: # If no gateway, calculate a default one
|
||||
current_ip_prefix = self.get_ip_prefix(current_ip)
|
||||
gateway_to_use = f"{current_ip_prefix}.1"
|
||||
self.log_message(f"No current gateway found, using default: {gateway_to_use}", "WARN")
|
||||
else:
|
||||
gateway_to_use = current_gateway
|
||||
|
||||
self.save_previous_config(if_name)
|
||||
|
||||
# Construir el comando netsh completo
|
||||
netsh_cmd = f'netsh interface ip set address "{if_name}" static {current_ip} {new_mask} {gateway_to_use}'
|
||||
self.log_message(f"Preparing to apply new mask. Command: {netsh_cmd}")
|
||||
|
||||
# execute_admin_script tomará netsh_cmd como 'mode' y lo pasará al script admin
|
||||
if self.execute_admin_script(if_name, mode=netsh_cmd, debug=True, subnet_mask=new_mask): # subnet_mask aquí es solo para logueo en debug
|
||||
time.sleep(2)
|
||||
# self.subnet_mask.set(new_mask) ya debería estar hecho por el usuario
|
||||
self.add_to_history(self.get_ip_prefix(current_ip)) # Esto usará new_mask de self.subnet_mask
|
||||
self.refresh_interfaces()
|
||||
self.log_message(f"Successfully applied subnet mask {new_mask} to {current_ip} on {if_name}.")
|
||||
else:
|
||||
self.show_error(f"Failed to apply subnet mask to {if_name}. Check log.")
|
||||
|
||||
|
||||
def create_context_menu(self):
|
||||
"""Crea el menú contextual para la ventana principal."""
|
||||
self.context_menu = tk.Menu(self.master, tearoff=0)
|
||||
self.context_menu.add_command(
|
||||
label="Open Project Folder", command=self.open_project_folder
|
||||
)
|
||||
|
||||
self.master.bind("<Button-3>", self.show_context_menu) # Button-3 es clic derecho
|
||||
|
||||
def show_context_menu(self, event):
|
||||
"""Muestra el menú contextual en la posición del cursor."""
|
||||
try:
|
||||
self.context_menu.tk_popup(event.x_root, event.y_root)
|
||||
finally:
|
||||
self.context_menu.grab_release()
|
||||
|
||||
def open_project_folder(self):
|
||||
"""Abre la carpeta del proyecto en el explorador de archivos."""
|
||||
try:
|
||||
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
subprocess.run(['explorer', script_dir]) # Quitado check=True
|
||||
self.log_message(f"Opened project folder: {script_dir}")
|
||||
except Exception as e:
|
||||
self.log_message(f"Error opening project folder: {e}", "ERROR")
|
||||
|
||||
def on_app_exit(self):
|
||||
"""Maneja el cierre de la aplicación, incluyendo la restauración del WindowProc."""
|
||||
self.master.destroy()
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = IPChangerApp(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"10.1.22": "10.1.22.11", "10.138.182": "10.138.182.94", "10.1.20": "10.1.33.11", "10.255.255": "10.255.255.1", "192.168.88": "192.168.88.1", "192.168.212": "192.168.212.212", "192.168.1": "192.168.1.212", "10.1.33": "192.168.88.1", "192.168.193": "192.168.1.212"}
|
||||
{"10.1.22": "10.1.22.11", "10.138.182": "10.138.182.94", "10.1.20": "10.1.33.11", "10.255.255": "10.255.255.1", "192.168.88": "192.168.88.1", "192.168.212": "192.168.212.212", "192.168.1": "192.168.1.212", "10.1.33": "192.168.88.1", "192.168.193": "192.168.1.212", "172.25.77": "172.25.77.1"}
|
Loading…
Reference in New Issue