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

# Генерация серийного номера для 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):
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_widget.insert(tk.END, f"[{current_time}] {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 stop_script(stop_event, log_widget):
    stop_event.set()
    add_log(log_widget, f"[INFO] Запрос на остановку выполнения скрипта.")

# Функция для запуска скрипта на одной виртуальной машине
def run_script(file_path, guest_user, guest_password, command, log_widget, num_runs, pause_duration, continuous, stop_event,
              upload_host_path, guest_upload_path, guest_download_path, host_download_path, host_output_path):
    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

    while not stop_event.is_set():
        # Проверяем, запущена ли виртуальная машина
        if is_vm_running(vmrun_path, file_path):
            add_log(log_widget, f"[INFO] Виртуальная машина уже запущена. Остановка виртуальной машины.")
            # Остановка виртуальной машины
            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] Виртуальная машина остановлена.")
            except subprocess.CalledProcessError as e:
                error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину: {error}")
                if not continuous:
                    break

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

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

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

        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] Виртуальная машина запущена.")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось запустить виртуальную машину: {error}")
            if not continuous:
                break

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

        # Извлечение N строк из хостового файла и загрузка на виртуальную машину
        if upload_host_path and guest_upload_path:
            try:
                with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                    lines = [next(host_file) for _ in range(num_runs)]
            except StopIteration:
                add_log(log_widget, f"[WARNING] В файле {upload_host_path} меньше строк, чем указано в итерациях ({num_runs}). Загрузка доступных строк.")
                with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                    lines = host_file.readlines()
            except Exception as e:
                add_log(log_widget, f"[ERROR] Не удалось прочитать файл для загрузки: {e}")
                if not continuous:
                    break
            else:
                # Запись извлечённых строк в временный файл для загрузки
                temp_upload_path = r"c:\icloudreg\temp_upload.txt"
                try:
                    with open(temp_upload_path, 'w', encoding='utf-8') as temp_file:
                        temp_file.writelines(lines)
                    add_log(log_widget, f"[INFO] Извлечено {len(lines)} строк для загрузки на виртуальную машину.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось записать временный файл для загрузки: {e}")
                    if not continuous:
                        break

                # Загрузка временного файла на виртуальную машину
                add_log(log_widget, f"[INFO] Загрузка строк на виртуальную машину.")
                copy_to_guest_cmd = [
                    vmrun_path, "-T", "ws",
                    "-gu", guest_user, "-gp", guest_password,
                    "copyFileFromHostToGuest",
                    file_path, temp_upload_path, guest_upload_path
                ]
                try:
                    subprocess.run(copy_to_guest_cmd, capture_output=True, text=True, check=True)
                    add_log(log_widget, f"[SUCCESS] Строки успешно загружены на виртуальную машину.")
                except subprocess.CalledProcessError as e:
                    error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                    add_log(log_widget, f"[ERROR] Не удалось загрузить строки на виртуальную машину: {error}")
                    if not continuous:
                        break

                # Удаление загруженных строк из хостового файла
                try:
                    remaining_lines = []
                    with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                        remaining_lines = host_file.readlines()[len(lines):]
                    with open(upload_host_path, 'w', encoding='utf-8') as host_file:
                        host_file.writelines(remaining_lines)
                    add_log(log_widget, f"[SUCCESS] Удалены загруженные строки из файла {upload_host_path}.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось удалить загруженные строки из файла {upload_host_path}: {e}")
                    if not continuous:
                        break
        else:
            add_log(log_widget, f"[WARNING] Пути для загрузки строк не указаны. Пропуск этапа загрузки.")

        # Добавляем паузу перед итерациями
        add_log(log_widget, f"[INFO] Ждем {pause_duration} секунд перед началом итераций...")
        for _ in range(pause_duration):
            if stop_event.is_set():
                break
            time.sleep(1)
        if stop_event.is_set():
            break

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

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

        # Запуск команды несколько раз с регулируемой паузой
        for i in range(num_runs):
            if stop_event.is_set():
                break

            # Логируем запуск команды
            add_log(log_widget, f"[INFO] Запуск команды {i+1}/{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}.\n  Вывод: {result.stdout.strip()}")
            except subprocess.CalledProcessError as e:
                if e.returncode == 1:
                    # Считаем выполнение успешным
                    add_log(log_widget, f"[SUCCESS] Команда выполнена {i+1}/{num_runs} (код выхода 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}:\nСтандартный вывод: {output}\nСтандартный вывод ошибок: {error_output}")
                    if not continuous:
                        break

            # Ожидание между запусками
            if i < num_runs - 1:
                add_log(log_widget, f"[INFO] Ожидание {pause_duration} секунд перед следующей итерацией команды...")
                for _ in range(pause_duration):
                    if stop_event.is_set():
                        break
                    time.sleep(1)
                if stop_event.is_set():
                    break

        # Выгрузка файла из гостевой системы на хостовую
        if guest_download_path and host_download_path:
            add_log(log_widget, f"[INFO] Выгрузка файла из гостевой системы на хостовую.")
            copy_to_host_cmd = [
                vmrun_path, "-T", "ws",
                "-gu", guest_user, "-gp", guest_password,
                "copyFileFromGuestToHost",
                file_path, guest_download_path, host_download_path
            ]
            try:
                subprocess.run(copy_to_host_cmd, capture_output=True, text=True, check=True)
                add_log(log_widget, f"[SUCCESS] Файл успешно выгружен с гостевой системы.")
            except subprocess.CalledProcessError as e:
                error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                add_log(log_widget, f"[ERROR] Не удалось выгрузить файл с гостевой системы: {error}")
                if not continuous:
                    break
        else:
            add_log(log_widget, f"[WARNING] Пути для выгрузки файла не указаны. Пропуск этапа выгрузки.")

        # Обработка выгруженного файла: извлечение всех строк и добавление в файл на хостовой системе
        if host_download_path and host_output_path:
            try:
                with open(host_download_path, 'r', encoding='utf-8') as downloaded_file:
                    lines = downloaded_file.readlines()
            except Exception as e:
                add_log(log_widget, f"[ERROR] Не удалось прочитать выгруженный файл {host_download_path}: {e}")
                if not continuous:
                    break
            else:
                try:
                    with open(host_output_path, 'a', encoding='utf-8') as output_file:
                        output_file.writelines(lines)
                    add_log(log_widget, f"[SUCCESS] Добавлено {len(lines)} строк из файла {host_download_path} в {host_output_path}.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось записать в файл {host_output_path}: {e}")
                    if not continuous:
                        break
        else:
            add_log(log_widget, f"[WARNING] Путь к файлу для записи на хостовой системе не указан. Пропуск этапа обработки выгруженного файла.")

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

        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] Виртуальная машина остановлена.")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину: {error}")
            if not continuous:
                break

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

        add_log(log_widget, f"[INFO] Завершено выполнение скрипта.")

        if not continuous:
            break  # Выходим из цикла, если не требуется непрерывная работа

# Обработка нажатия кнопки "Запустить Скрипт"
def handle_run_script(file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget,
                     num_runs_entry, pause_entry, continuous_var, stop_event,
                     upload_host_entry, guest_upload_entry, guest_download_entry, host_download_entry,
                     host_output_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()
    pause_duration_str = pause_entry.get().strip()
    continuous = continuous_var.get()
    upload_host_path = upload_host_entry.get().strip()
    guest_upload_path = guest_upload_entry.get().strip()
    guest_download_path = guest_download_entry.get().strip()
    host_download_path = host_download_entry.get().strip()
    host_output_path = host_output_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
    if not pause_duration_str.isdigit() or int(pause_duration_str) < 0:
        messagebox.showwarning("Внимание", "Пожалуйста, введите корректную паузу в секундах (целое число 0 или больше).")
        return

    num_runs = int(num_runs)
    pause_duration = int(pause_duration_str)

    # Создаем событие остановки
    stop_event.clear()

    # Запускаем функцию run_script в отдельном потоке
    thread = threading.Thread(target=run_script, args=(
        file_path, guest_user, guest_password, command, log_widget,
        num_runs, pause_duration, continuous, stop_event,
        upload_host_path, guest_upload_path, guest_download_path, host_download_path,
        host_output_path))
    thread.daemon = True
    thread.start()

    mode = "Непрерывной работы" if continuous else "однократного выполнения"
    add_log(log_widget, f"[INFO] Запуск выполнения скрипта с параметрами:\n"
                        f"- Файл конфигурации: {file_path}\n"
                        f"- Количество запусков команды: {num_runs}\n"
                        f"- Пауза между итерациями: {pause_duration} секунд\n"
                        f"- Режим: {mode}")

# Диалог выбора файла
def browse_file(file_entry):
    user_profile = os.environ.get('USERPROFILE', r'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 browse_upload_host(upload_host_entry):
    file_path = filedialog.askopenfilename(
        title="Выберите текстовый файл для загрузки на гостевую систему",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        upload_host_entry.delete(0, tk.END)
        upload_host_entry.insert(0, file_path)

# Диалог выбора файла на хостовой системе для сохранения выгрузки
def browse_download_host(host_download_entry):
    file_path = filedialog.asksaveasfilename(
        title="Выберите место для сохранения выгруженного файла",
        defaultextension=".txt",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        host_download_entry.delete(0, tk.END)
        host_download_entry.insert(0, file_path)

# Диалог выбора выходного файла на хостовой системе для сохранения извлечённых строк
def browse_output_host(host_output_entry):
    file_path = filedialog.asksaveasfilename(
        title="Выберите место для сохранения выходного файла",
        defaultextension=".txt",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        host_output_entry.delete(0, tk.END)
        host_output_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):
    try:
        menu.tk_popup(event.x_root, event.y_root)
    finally:
        menu.grab_release()

# Главная функция создания GUI
def main():
    root = tk.Tk()
    root.title("Генератор Серийного Номера и Управление Виртуальной Машиной")
    root.geometry("1300x950")
    root.resizable(True, True)

    # Переменные и виджеты
    file_entry = tk.Entry(root, width=80)
    guest_user_entry = tk.Entry(root, width=30)
    guest_pass_entry = tk.Entry(root, width=30, show="*")  # Сокрытие пароля
    command_entry = tk.Entry(root, width=80)
    num_runs_entry = tk.Entry(root, width=15)
    pause_entry = tk.Entry(root, width=15)
    continuous_var = tk.BooleanVar(value=False)
    log_widget = scrolledtext.ScrolledText(root, width=160, height=25)
    stop_event = threading.Event()
    upload_host_entry = tk.Entry(root, width=80)
    guest_upload_entry = tk.Entry(root, width=80)
    guest_download_entry = tk.Entry(root, width=80)
    host_download_entry = tk.Entry(root, width=80)
    host_output_entry = tk.Entry(root, width=80)

    # Установка предопределённых путей
    # Файл на хостовой системе для загрузки на виртуалку
    upload_host_entry.insert(0, r"c:\icloudreg\out.txt")
    # Путь на гостевой системе для записи
    guest_upload_entry.insert(0, "/Users/deno/Desktop/flow.txt")
    # Файл для записи на хостовой системе
    host_download_entry.insert(0, r"c:\icloudreg\ok.txt")
    # Файл на виртуальной системе для выгрузки
    guest_download_entry.insert(0, "/Users/deno/Desktop/activated vm1.txt")

    # Создание виджетов
    # Поля для конфигурации виртуальной машины
    tk.Label(root, text="Путь к файлу конфигурации VMX:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
    file_entry.grid(row=0, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_file(file_entry)).grid(row=0, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(file_entry)

    tk.Label(root, text="Гость Username:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
    guest_user_entry.insert(0, "deno")  # Значение по умолчанию
    guest_user_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
    create_context_menu(guest_user_entry)

    tk.Label(root, text="Гость Password:").grid(row=1, column=2, padx=5, pady=5, sticky='e')
    guest_pass_entry.insert(0, "qwas")  # Значение по умолчанию
    guest_pass_entry.grid(row=1, column=3, padx=5, pady=5, sticky='w')
    create_context_menu(guest_pass_entry)

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

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

    tk.Label(root, text="Пауза между итерациями (секунды):").grid(row=3, column=2, padx=5, pady=5, sticky='e')
    pause_entry.insert(0, "60")  # Значение по умолчанию
    pause_entry.grid(row=3, column=3, padx=5, pady=5, sticky='w')
    create_context_menu(pause_entry)

    # Поля для загрузки файла на гостевую систему
    tk.Label(root, text="Файл для загрузки на гостевую систему:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
    upload_host_entry.grid(row=4, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_upload_host(upload_host_entry)).grid(row=4, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(upload_host_entry)

    tk.Label(root, text="Путь на гостевой системе для загрузки файла:").grid(row=5, column=0, padx=5, pady=5, sticky='e')
    guest_upload_entry.grid(row=5, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    create_context_menu(guest_upload_entry)

    # Поля для выгрузки файла из гостевой системы
    tk.Label(root, text="Путь к файлу на гостевой системе для выгрузки:").grid(row=6, column=0, padx=5, pady=5, sticky='e')
    guest_download_entry.grid(row=6, column=1, padx=5, pady=5, columnspan=2, sticky='w')
    create_context_menu(guest_download_entry)

    tk.Label(root, text="Путь на хостовой системе для сохранения выгруженного файла:").grid(row=6, column=3, padx=5, pady=5, sticky='e')
    host_download_entry.grid(row=6, column=4, padx=5, pady=5, columnspan=1, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_download_host(host_download_entry)).grid(row=6, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(host_download_entry)

    # Поле для выбора выходного файла на хостовой системе для сохранения извлечённых строк
    tk.Label(root, text="Путь к выходному файлу на хостовой системе для сохранения извлечённых строк:").grid(row=7, column=0, padx=5, pady=5, sticky='e')
    host_output_entry.grid(row=7, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_output_host(host_output_entry)).grid(row=7, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(host_output_entry)

    # Галочка для непрерывной работы
    continuous_checkbox = tk.Checkbutton(root, text="Непрерывная работа", variable=continuous_var)
    continuous_checkbox.grid(row=8, column=0, padx=5, pady=5, sticky='w')

    # Кнопки запуска и остановки
    tk.Button(root, text="Запустить Скрипт", command=lambda: handle_run_script(
        file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget,
        num_runs_entry, pause_entry, continuous_var, stop_event,
        upload_host_entry, guest_upload_entry, guest_download_entry, host_download_entry,
        host_output_entry
    )).grid(row=8, column=1, padx=5, pady=5, sticky='w')

    tk.Button(root, text="Стоп", command=lambda: stop_script(stop_event, log_widget),
              bg="red", fg="white").grid(row=8, column=2, padx=5, pady=5, sticky='w')

    # Логи
    tk.Label(root, text="Логи:", font=('Arial', 12, 'bold')).grid(row=9, column=0, padx=5, pady=5, sticky='w')
    log_widget.grid(row=10, 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

    root.mainloop()

if __name__ == "__main__":
    main()
Сделай чтобы все строки шли горизонтально одна за одной