import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk
import random
import re
import os
import subprocess
import shutil
import threading
import time

# Генерация серийного номера для iMacPro1,1
def generate_serial_number():
    base = "C02"
    week_code = random.choice(["T", "U", "V", "W", "X", "Y"])
    model_code = "41"
    location_code = random.choice(["KY", "KX", "KW", "KV", "KU", "KT"])
    unique_id = ''.join(random.choices('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=4))
    return f"{base}{week_code}{model_code}{location_code}{unique_id}"

# Генерация последних 3 октетов MAC-адреса
def generate_mac_suffix():
    return ':'.join(['{:02X}'.format(random.randint(0, 255)) for _ in range(3)])

# Обновление конфигурационного файла виртуальной машины
def update_config_file(file_path):
    backup_path = file_path + ".bak"
    shutil.copyfile(file_path, backup_path)

    if not os.path.isfile(file_path):
        raise FileNotFoundError(f"Файл не найден: {file_path}")

    with open(file_path, 'r', encoding='windows-1251') as file:
        config_lines = file.readlines()

    new_serial = generate_serial_number()
    new_mac_suffix = generate_mac_suffix()

    new_config = []
    for line in config_lines:
        if line.startswith("serialNumber = "):
            line = f'serialNumber = "{new_serial}"\n'
        elif line.startswith("ethernet0.Address = "):
            current_mac = re.search(r'"([^"]*)"', line).group(1)
            mac_parts = current_mac.split(':')
            if len(mac_parts) == 6:
                new_mac = ':'.join(mac_parts[:3] + new_mac_suffix.split(':'))
                line = f'ethernet0.Address = "{new_mac}"\n'
        new_config.append(line)

    with open(file_path, 'w', encoding='windows-1251') as file:
        file.writelines(new_config)

    return new_serial, new_mac_suffix

# Добавление сообщений в лог
def add_log(log_widget, message):
    log_widget.insert(tk.END, message + "\n")
    log_widget.see(tk.END)

# Предотвращение редактирования лога
def disable_log_edit(event):
    return "break"

# Проверка готовности гостевой ОС по состоянию VMware Tools
def wait_for_tools(vmrun_path, file_path, timeout=300):
    check_cmd = [vmrun_path, "checkToolsState", file_path]
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            result = subprocess.run(check_cmd, capture_output=True, text=True, check=True)
            if "running" in result.stdout.lower():
                return True
        except subprocess.CalledProcessError:
            pass
        time.sleep(5)
    return False

# Функция ожидания остановки виртуальной машины
def wait_for_vm_to_stop(vmrun_path, file_path, timeout=300):
    list_cmd = [vmrun_path, "list"]
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            result = subprocess.run(list_cmd, capture_output=True, text=True, check=True)
            running_vms = result.stdout.strip().split('\n')[1:]
            if file_path not in running_vms:
                return True
        except subprocess.CalledProcessError:
            pass
        time.sleep(5)
    return False

# Проверка состояния виртуальной машины
def is_vm_running(vmrun_path, file_path):
    list_cmd = [vmrun_path, "list"]
    try:
        result = subprocess.run(list_cmd, capture_output=True, text=True, check=True)
        running_vms = result.stdout.strip().split('\n')[1:]  # Первой строкой идет количество VMs
        return file_path in running_vms
    except subprocess.CalledProcessError:
        return False

# Запуск скрипта определенное количество раз
def run_additional_script(file_path, guest_user, guest_password, command, log_widget, num_runs):
    vmrun_path = r"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"

    if not os.path.isfile(vmrun_path):
        add_log(log_widget, f"[ERROR] vmrun.exe не найден по пути: {vmrun_path}")
        return

    if not os.path.isfile(file_path):
        add_log(log_widget, f"[ERROR] Файл VMX не найден: {file_path}")
        return

    # Проверяем, запущена ли виртуальная машина
    if is_vm_running(vmrun_path, file_path):
        add_log(log_widget, f"[INFO] Виртуальная машина уже запущена. Остановка виртуальной машины: {file_path}")
        # Остановка виртуальной машины
        try:
            stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
            subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена: {file_path}")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину {file_path}: {error}")
            return

        # Ожидание полной остановки виртуальной машины
        add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
        if not wait_for_vm_to_stop(vmrun_path, file_path):
            add_log(log_widget, f"[ERROR] Виртуальная машина {file_path} не остановлена. Превышено время ожидания.")
            return
        else:
            add_log(log_widget, f"[SUCCESS] Виртуальная машина {file_path} успешно остановлена.")

    # Генерация и обновление конфигурации перед запуском виртуальной машины
    try:
        serial, mac_suffix = update_config_file(file_path)
        add_log(log_widget, f"[SUCCESS] Обновлен файл конфигурации: {file_path}\n  Новый серийный номер: {serial}\n  Новый MAC-адрес: {mac_suffix}")
    except Exception as e:
        add_log(log_widget, f"[ERROR] При обновлении файла конфигурации {file_path}: {e}")
        return

    # Запуск виртуальной машины
    add_log(log_widget, f"[INFO] Запуск виртуальной машины: {file_path}")

    try:
        start_cmd = [vmrun_path, "-T", "ws", "start", file_path, "nogui"]
        subprocess.run(start_cmd, capture_output=True, text=True, check=True)
        add_log(log_widget, f"[SUCCESS] Виртуальная машина запущена: {file_path}")
    except subprocess.CalledProcessError as e:
        error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
        add_log(log_widget, f"[ERROR] Не удалось запустить виртуальную машину {file_path}: {error}")
        return

    # Ожидание готовности гостевой ОС
    add_log(log_widget, f"[INFO] Ожидание готовности гостевой ОС...")
    if not wait_for_tools(vmrun_path, file_path):
        add_log(log_widget, f"[ERROR] Гостевая ОС не готова для выполнения команд. Превышено время ожидания.")
        return
    add_log(log_widget, f"[SUCCESS] Гостевая ОС готова для выполнения команд.")

    # Добавляем паузу в 2 минуты
    add_log(log_widget, f"[INFO] Ожидание 2 минут для полной загрузки гостевой ОС...")
    time.sleep(120)

    # Парсинг команды на программу и аргументы
    command_parts = command.strip().split()
    if len(command_parts) == 0:
        add_log(log_widget, f"[ERROR] Команда для выполнения пуста.")
        return

    program_path = command_parts[0]
    program_args = command_parts[1:]  # Может быть пустым списком

    # Запуск команды несколько раз
    for i in range(num_runs):
        # Логируем запуск AppleScript
        add_log(log_widget, f"[INFO] Запуск AppleScript {i+1}/{num_runs} для файла: {file_path}")

        cmd = [
            vmrun_path, "-T", "ws",
            "-gu", guest_user, "-gp", guest_password,
            "runProgramInGuest",
            file_path, program_path
        ] + program_args

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] AppleScript выполнен {i+1}/{num_runs} для файла: {file_path}\n  Вывод: {result.stdout.strip()}")
        except subprocess.CalledProcessError as e:
            # Проверяем код возврата
            if e.returncode == 1:
                # Считаем выполнение успешным
                add_log(log_widget, f"[SUCCESS] AppleScript выполнен {i+1}/{num_runs} для файла: {file_path} (код выхода 1)")
            else:
                error_output = e.stderr.strip() if e.stderr else "Нет вывода ошибки."
                output = e.stdout.strip() if e.stdout else "Нет вывода."
                add_log(log_widget, f"[ERROR] При выполнении команды {i+1}/{num_runs} для файла {file_path}:\nСтандартный вывод: {output}\nСтандартный вывод ошибок: {error_output}")
                break  # Прерывание при критической ошибке

        # Ожидание между запусками
        time.sleep(5)

    # Остановка виртуальной машины
    add_log(log_widget, f"[INFO] Остановка виртуальной машины: {file_path}")

    try:
        stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
        subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
        add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена: {file_path}")
    except subprocess.CalledProcessError as e:
        error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
        add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину {file_path}: {error}")
        return

    # Ожидание полной остановки виртуальной машины
    add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
    if not wait_for_vm_to_stop(vmrun_path, file_path):
        add_log(log_widget, f"[ERROR] Виртуальная машина {file_path} не остановлена. Превышено время ожидания.")
    else:
        add_log(log_widget, f"[SUCCESS] Виртуальная машина {file_path} успешно остановлена.")

    add_log(log_widget, f"[INFO] Завершено выполнение скрипта для файла: {file_path}")

    # Генерация и обновление конфигурации перед запуском виртуальной машины
    try:
        serial, mac_suffix = update_config_file(file_path)
        add_log(log_widget, f"[SUCCESS] Обновлен файл конфигурации: {file_path}\n  Новый серийный номер: {serial}\n  Новый MAC-адрес: {mac_suffix}")
    except Exception as e:
        add_log(log_widget, f"[ERROR] При обновлении файла конфигурации {file_path}: {e}")
        return

    # Запуск виртуальной машины
    add_log(log_widget, f"[INFO] Запуск виртуальной машины: {file_path}")

    try:
        start_cmd = [vmrun_path, "-T", "ws", "start", file_path, "nogui"]
        subprocess.run(start_cmd, capture_output=True, text=True, check=True)
        add_log(log_widget, f"[SUCCESS] Виртуальная машина запущена: {file_path}")
    except subprocess.CalledProcessError as e:
        error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
        add_log(log_widget, f"[ERROR] Не удалось запустить виртуальную машину {file_path}: {error}")
        return

    # Ожидание готовности гостевой ОС
    add_log(log_widget, f"[INFO] Ожидание готовности гостевой ОС...")
    if not wait_for_tools(vmrun_path, file_path):
        add_log(log_widget, f"[ERROR] Гостевая ОС не готова для выполнения команд. Превышено время ожидания.")
        return
    add_log(log_widget, f"[SUCCESS] Гостевая ОС готова для выполнения команд.")

    # Добавляем паузу в 1 минуты
    add_log(log_widget, f"[INFO] Ожидание 1 минуты для полной загрузки гостевой ОС...")
    time.sleep(60)

    # Парсинг команды на программу и аргументы
    command_parts = command.strip().split()
    if len(command_parts) == 0:
        add_log(log_widget, f"[ERROR] Команда для выполнения пуста.")
        return

    program_path = command_parts[0]
    program_args = command_parts[1:]  # Может быть пустым списком

    # Запуск команды несколько раз
    for i in range(num_runs):
        cmd = [
            vmrun_path, "-T", "ws",
            "-gu", guest_user, "-gp", guest_password,
            "runProgramInGuest",
            file_path, program_path
        ] + program_args

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] Команда выполнена {i+1}/{num_runs} для файла: {file_path}\n  Вывод: {result.stdout.strip()}")
        except subprocess.CalledProcessError as e:
            error_output = e.stderr.strip() if e.stderr else "Нет вывода ошибки."
            output = e.stdout.strip() if e.stdout else "Нет вывода."
            add_log(log_widget, f"[ERROR] При выполнении команды {i+1}/{num_runs} для файла {file_path}:\nСтандартный вывод: {output}\nСтандартный вывод ошибок: {error_output}")
            break  # Прерывание при ошибке

        # Ожидание между запусками
        time.sleep(5)

    # Остановка виртуальной машины
    add_log(log_widget, f"[INFO] Остановка виртуальной машины: {file_path}")

    try:
        stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
        subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
        add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена: {file_path}")
    except subprocess.CalledProcessError as e:
        error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
        add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину {file_path}: {error}")
        return

    # Ожидание полной остановки виртуальной машины
    add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
    if not wait_for_vm_to_stop(vmrun_path, file_path):
        add_log(log_widget, f"[ERROR] Виртуальная машина {file_path} не остановлена. Превышено время ожидания.")
    else:
        add_log(log_widget, f"[SUCCESS] Виртуальная машина {file_path} успешно остановлена.")

    add_log(log_widget, f"[INFO] Завершено выполнение скрипта для файла: {file_path}")

# Функция проверки команды
def test_command(file_path, guest_user, guest_password, command, log_widget):
    vmrun_path = r"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"

    if not os.path.isfile(vmrun_path):
        add_log(log_widget, f"[ERROR] vmrun.exe не найден по пути: {vmrun_path}")
        return

    if not os.path.isfile(file_path):
        add_log(log_widget, f"[ERROR] Файл VMX не найден: {file_path}")
        return

    vm_was_running = is_vm_running(vmrun_path, file_path)

    # Если виртуальная машина не запущена, запускаем ее
    if not vm_was_running:
        add_log(log_widget, f"[INFO] Виртуальная машина не запущена. Запуск виртуальной машины для проверки: {file_path}")
        try:
            start_cmd = [vmrun_path, "-T", "ws", "start", file_path, "nogui"]
            subprocess.run(start_cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] Виртуальная машина запущена: {file_path}")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось запустить виртуальную машину {file_path}: {error}")
            return

        # Ожидание готовности гостевой ОС
        add_log(log_widget, f"[INFO] Ожидание готовности гостевой ОС...")
        if not wait_for_tools(vmrun_path, file_path):
            add_log(log_widget, f"[ERROR] Гостевая ОС не готова для выполнения команд. Превышено время ожидания.")
            return
        add_log(log_widget, f"[SUCCESS] Гостевая ОС готова для выполнения команд.")

    # Парсинг команды на программу и аргументы
    command_parts = command.strip().split()
    if len(command_parts) == 0:
        add_log(log_widget, f"[ERROR] Команда для выполнения пуста.")
        return

    program_path = command_parts[0]
    program_args = command_parts[1:]  # Может быть пустым списком

    # Запуск команды один раз
    cmd = [
        vmrun_path, "-T", "ws",
        "-gu", guest_user, "-gp", guest_password,
        "runProgramInGuest",
        file_path, program_path
    ] + program_args

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        add_log(log_widget, f"[SUCCESS] Команда успешно выполнена для файла: {file_path}\n  Вывод: {result.stdout.strip()}")
    except subprocess.CalledProcessError as e:
        error_output = e.stderr.strip() if e.stderr else "Нет вывода ошибки."
        output = e.stdout.strip() if e.stdout else "Нет вывода."
        add_log(log_widget, f"[ERROR] При выполнении команды для файла {file_path}:\nСтандартный вывод: {output}\nСтандартный вывод ошибок: {error_output}")
    finally:
        # Если VM не была запущена изначально, останавливаем ее
        if not vm_was_running:
            add_log(log_widget, f"[INFO] Остановка виртуальной машины после проверки: {file_path}")
            try:
                stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
                subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
                add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена: {file_path}")
            except subprocess.CalledProcessError as e:
                error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину {file_path}: {error}")
                return

            # Ожидание полной остановки виртуальной машины
            add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
            if not wait_for_vm_to_stop(vmrun_path, file_path):
                add_log(log_widget, f"[ERROR] Виртуальная машина {file_path} не остановлена. Превышено время ожидания.")
            else:
                add_log(log_widget, f"[SUCCESS] Виртуальная машина {file_path} успешно остановлена.")

def handle_test_command(file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget):
    file_path = file_entry.get().strip()
    guest_user = guest_user_entry.get().strip()
    guest_password = guest_pass_entry.get().strip()
    command = command_entry.get().strip()

    if not file_path:
        messagebox.showwarning("Внимание", "Пожалуйста, выберите файл конфигурации.")
        return
    if not guest_user or not guest_password:
        messagebox.showwarning("Внимание", "Пожалуйста, введите гостевые учетные данные.")
        return
    if not command:
        messagebox.showwarning("Внимание", "Пожалуйста, введите команду для выполнения.")
        return

    thread = threading.Thread(target=test_command, args=(file_path, guest_user, guest_password, command, log_widget))
    thread.daemon = True
    thread.start()

    add_log(log_widget, f"[INFO] Проверка команды для файла: {file_path}")

# Обработка нажатия кнопки "Запустить Скрипт"
def handle_run_script(file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget, num_runs_entry):
    file_path = file_entry.get().strip()
    guest_user = guest_user_entry.get().strip()
    guest_password = guest_pass_entry.get().strip()
    command = command_entry.get().strip()
    num_runs = num_runs_entry.get().strip()

    if not file_path:
        messagebox.showwarning("Внимание", "Пожалуйста, выберите файл конфигурации.")
        return
    if not guest_user or not guest_password:
        messagebox.showwarning("Внимание", "Пожалуйста, введите гостевые учетные данные.")
        return
    if not command:
        messagebox.showwarning("Внимание", "Пожалуйста, введите команду для выполнения.")
        return
    if not num_runs.isdigit() or int(num_runs) < 1:
        messagebox.showwarning("Внимание", "Пожалуйста, введите корректное количество запусков (целое число больше 0).")
        return

    num_runs = int(num_runs)

    thread = threading.Thread(target=run_additional_script, args=(file_path, guest_user, guest_password, command, log_widget, num_runs))
    thread.daemon = True
    thread.start()

    add_log(log_widget, f"[INFO] Запуск выполнения скрипта для файла: {file_path}")

# Остальная часть кода остается без изменений

# Диалог выбора файла
def browse_file(file_entry):
    user_profile = os.environ.get('USERPROFILE', 'C:\\Users\\Default')
    default_dir = os.path.join(user_profile, 'Documents', 'Virtual Machines')
    file_path = filedialog.askopenfilename(
        title="Выберите конфигурационный файл VMX",
        initialdir=default_dir,
        filetypes=[("VMX файлы", "*.vmx"), ("Все файлы", "*.*")]
    )
    if file_path:
        file_entry.delete(0, tk.END)
        file_entry.insert(0, file_path)

# Функции для контекстного меню (копировать, вставить, вырезать)
def create_context_menu(widget):
    context_menu = tk.Menu(widget, tearoff=0)
    context_menu.add_command(label="Копировать", command=lambda: widget.event_generate('<<Copy>>'))
    context_menu.add_command(label="Вставить", command=lambda: widget.event_generate('<<Paste>>'))
    context_menu.add_command(label="Вырезать", command=lambda: widget.event_generate('<<Cut>>'))
    widget.bind("<Button-3>", lambda event: show_context_menu(event, context_menu))

def show_context_menu(event, menu):
    menu.tk_popup(event.x_root, event.y_root)

# Создание одной вкладки для VM
def create_vm_tab(notebook, tab_num, widgets):
    tab = ttk.Frame(notebook)
    notebook.add(tab, text=f"VM {tab_num+1}")

    # Переменная для галочки выбора
    selected_var = tk.BooleanVar(value=False)
    widgets['selected'].append(selected_var)

    # Галочка выбора виртуальной машины
    select_checkbox = tk.Checkbutton(tab, text="Выбрать для запуска", variable=selected_var)
    select_checkbox.grid(row=0, column=0, padx=5, pady=5, sticky='w')

    # Путь к файлу конфигурации
    tk.Label(tab, text="Путь к файлу конфигурации:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
    file_entry = tk.Entry(tab, width=60)
    file_entry.grid(row=1, column=1, padx=5, pady=5, columnspan=2, sticky='w')
    tk.Button(tab, text="Обзор", command=lambda: browse_file(file_entry)).grid(row=1, column=3, padx=5, pady=5, sticky='w')

    create_context_menu(file_entry)  # Добавление контекстного меню в поле ввода

    ttk.Separator(tab, orient='horizontal').grid(row=2, column=0, columnspan=6, sticky='ew', padx=5, pady=10)

    # Гостевые учетные данные и команда
    tk.Label(tab, text="Гость Username:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
    guest_user_entry = tk.Entry(tab, width=25)
    guest_user_entry.insert(0, "deno")  # Значение по умолчанию
    guest_user_entry.grid(row=3, column=1, padx=5, pady=5, sticky='w')
    create_context_menu(guest_user_entry)  # Добавление контекстного меню

    tk.Label(tab, text="Гость Password:").grid(row=3, column=2, padx=5, pady=5, sticky='e')
    guest_pass_entry = tk.Entry(tab, width=25, show="*")
    guest_pass_entry.insert(0, "qwas")  # Значение по умолчанию
    guest_pass_entry.grid(row=3, column=3, padx=5, pady=5, sticky='w')
    create_context_menu(guest_pass_entry)  # Добавление контекстного меню

    tk.Label(tab, text="Команда для выполнения:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
    command_entry = tk.Entry(tab, width=60)
    command_entry.insert(0, "/usr/bin/osascript /Users/Deno/ISMS.applescript")  # Значение по умолчанию
    command_entry.grid(row=4, column=1, padx=5, pady=5, columnspan=3, sticky='w')
    create_context_menu(command_entry)  # Добавление контекстного меню

    # Поле для ввода количества запусков команды
    tk.Label(tab, text="Количество запусков команды:").grid(row=5, column=0, padx=5, pady=5, sticky='e')
    num_runs_entry = tk.Entry(tab, width=10)
    num_runs_entry.insert(0, "5")  # Значение по умолчанию
    num_runs_entry.grid(row=5, column=1, padx=5, pady=5, sticky='w')
    create_context_menu(num_runs_entry)  # Добавление контекстного меню

    # Кнопки запуска скрипта и проверки
    tk.Button(tab, text="Запустить Скрипт", command=lambda: handle_run_script(
        file_entry, guest_user_entry, guest_pass_entry, command_entry, widgets['log'][tab_num], num_runs_entry
    )).grid(row=6, column=1, padx=5, pady=5, sticky='w')

    tk.Button(tab, text="Проверить", command=lambda: handle_test_command(
        file_entry, guest_user_entry, guest_pass_entry, command_entry, widgets['log'][tab_num]
    )).grid(row=6, column=2, padx=5, pady=5, sticky='w')

    # Логи
    tk.Label(tab, text="Логи:", font=('Arial', 10, 'bold')).grid(row=7, column=0, padx=5, pady=5, sticky='w')
    log_widget = scrolledtext.ScrolledText(tab, width=100, height=20)
    log_widget.grid(row=8, column=0, columnspan=6, padx=5, pady=5)
    create_context_menu(log_widget)  # Добавление контекстного меню в поле лога

    # Предотвращение редактирования лога
    log_widget.bind("<Key>", disable_log_edit)
    log_widget.bind("<Insert>", disable_log_edit)  # Блокировка клавиши Insert

    widgets['log'].append(log_widget)

    # Сохранение виджетов
    widgets['file'].append(file_entry)
    widgets['guest_user'].append(guest_user_entry)
    widgets['guest_pass'].append(guest_pass_entry)
    widgets['command'].append(command_entry)
    widgets['num_runs'].append(num_runs_entry)

# Запуск скриптов на всех выбранных VM
def run_all(file_entries, guest_user_entries, guest_pass_entries, command_entries, log_widgets, selected_vars, num_runs_entries):
    for i in range(len(file_entries)):
        if selected_vars[i].get():  # Проверяем, выбрана ли VM
            file_path = file_entries[i].get().strip()
            guest_user = guest_user_entries[i].get().strip()
            guest_password = guest_pass_entries[i].get().strip()
            command = command_entries[i].get().strip()
            num_runs_str = num_runs_entries[i].get().strip()
            log_widget = log_widgets[i]

            if not num_runs_str.isdigit() or int(num_runs_str) < 1:
                add_log(log_widget, f"[WARNING] Некорректное количество запусков для VM {i+1}. Пропуск.")
                continue
            num_runs = int(num_runs_str)

            if file_path and guest_user and guest_password and command:
                thread = threading.Thread(target=run_additional_script, args=(file_path, guest_user, guest_password, command, log_widget, num_runs))
                thread.daemon = True
                thread.start()
                add_log(log_widget, f"[INFO] Запуск выполнения скрипта для файла: {file_path}")
            else:
                add_log(log_widget, f"[WARNING] Необходимые данные не заполнены для VM {i+1}. Пропуск.")

# Создание кнопки "Запустить на всех"
def create_run_all_button(parent, widgets):
    tk.Button(parent, text="Запустить на всех", command=lambda: run_all(
        widgets['file'], widgets['guest_user'], widgets['guest_pass'],
        widgets['command'], widgets['log'], widgets['selected'], widgets['num_runs']
    ), bg="lightgreen", font=('Arial', 12, 'bold')).pack(pady=10)

# Главная функция создания GUI
def main():
    widgets = {
        'file': [], 'guest_user': [], 'guest_pass': [],
        'command': [], 'log': [], 'selected': [], 'num_runs': []
    }

    root = tk.Tk()
    root.title("Генератор Серийных Номеров и Управление Виртуальными Машинами")
    root.geometry("1200x800")
    root.resizable(True, True)

    notebook = ttk.Notebook(root)
    notebook.pack(pady=10, padx=10, fill='both', expand=True)

    for i in range(10):
        create_vm_tab(notebook, i, widgets)

    create_run_all_button(root, widgets)

    root.mainloop()

if __name__ == "__main__":
    main()
Добавь регулируемую паузу между итерациями