делаю менеджер паролей
gui.py и password_manager.py


gui.py:

# gui.py

import tkinter as tk
from tkinter import messagebox, ttk, scrolledtext, filedialog
from PIL import Image, ImageTk
import pyperclip
import logging
from password_manager import (
    load_data, save_data, add_password, get_all_passwords,
    delete_password_entry_by_id, generate_password, check_password_strength,
    export_to_csv, import_from_csv, delete_all_passwords  # Импортируем delete_all_passwords
)

FILE_PATH = 'data.enc'

# Настройка логирования (дублируем настройки из password_manager.py для консистентности)
app_logger = logging.getLogger('app_logger')
audit_logger = logging.getLogger('audit_logger')


class PasswordManagerApp:
    def __init__(self, root):  # Исправлено имя конструктора
        self.root = root
        self.root.title("Менеджер Паролей")
        self.root.geometry("1000x700")  # Увеличена высота для удобства
        self.root.resizable(False, False)
        self.data = None
        self.master_password = None

        # Переменная для отслеживания открытия окна добавления пароля
        self.add_password_window_open = False

        # Настройка стилей
        self.setup_styles()

        # Загрузка фона
        self.load_background()

        self.create_login_window()

    def setup_styles(self):
        style = ttk.Style()
        style.theme_use('clam')  # Вы можете выбрать 'clam', 'alt', 'default', 'classic'

        # Настройка заголовков
        style.configure("TLabel", font=("Arial", 12), foreground="white")
        style.configure("Header.TLabel", font=("Arial", 20, "bold"), foreground="#00BFFF")

        # Настройка кнопок
        style.configure("TButton",
                        font=("Arial", 12),
                        padding=6,
                        relief="flat",
                        background="#00BFFF",
                        foreground="white")
        style.map("TButton",
                  background=[('active', '#1E90FF')],
                  foreground=[('active', 'white')])

        # Настройка Treeview
        style.configure("Treeview.Heading", font=("Arial", 12, "bold"), foreground="#00BFFF")
        style.configure("Treeview", font=("Arial", 10),
                        rowheight=25,
                        fieldbackground="#2C3E50",
                        background="#34495E",
                        foreground="white",
                        highlightthickness=0,
                        bd=0)
        style.map('Treeview', background=[('selected', '#1ABC9C')], foreground=[('selected', 'white')])

        # Настройка Progressbar
        style.configure("TProgressbar", thickness=20)


    def load_background(self):
        try:
            # Загрузите ваше фоновое изображение
            self.background_image = Image.open("background.png")
            self.background_image = self.background_image.resize((1000, 700), Image.ANTIALIAS)
            self.bg_photo = ImageTk.PhotoImage(self.background_image)

            self.background_label = tk.Label(self.root, image=self.bg_photo)
            self.background_label.place(x=0, y=0, relwidth=1, relheight=1)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить фоновое изображение: {e}")
            self.background_label = None

    def create_login_window(self):
        self.clear_window()

        frame = ttk.Frame(self.root, padding="40 40 40 40", style="TFrame")
        frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

        header = ttk.Label(frame, text="### Менеджер Паролей ###", style="Header.TLabel")
        header.pack(pady=(0, 20))

        password_label = ttk.Label(frame, text="Введите мастер-пароль:")
        password_label.pack(pady=(0, 10))

        self.password_entry = ttk.Entry(frame, show="*", width=40, font=("Arial", 12))
        self.password_entry.pack(pady=(0, 20))

        # Загрузка иконки для кнопки "Войти"
        try:
            login_icon = Image.open("view_icon.png")
            login_icon = login_icon.resize((20, 20), Image.ANTIALIAS)
            self.login_icon_photo = ImageTk.PhotoImage(login_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Войти': {e}")
            self.login_icon_photo = None

        login_button = ttk.Button(frame, text="Войти", image=self.login_icon_photo, compound=tk.LEFT, command=self.login)
        if self.login_icon_photo:
            login_button.image = self.login_icon_photo  # Сохраняем ссылку на изображение
        login_button.pack()

        # Обработка нажатия Enter для входа
        self.password_entry.bind("<Return>", lambda event: self.login())

    def login(self):
        password = self.password_entry.get()
        if not password:
            messagebox.showerror("Ошибка", "Мастер-пароль не может быть пустым.", parent=self.root)
            return
        try:
            self.data = load_data(FILE_PATH, password)
            self.master_password = password
            app_logger.info("Пользователь успешно вошёл в систему.")
            audit_logger.info("Пользователь вошёл в систему.")
            self.create_main_window()
        except ValueError as ve:
            messagebox.showerror("Ошибка", str(ve), parent=self.root)
            app_logger.warning(f"Ошибка при входе: {ve}")
            audit_logger.warning(f"Попытка входа с неверным паролем.")
        except Exception as e:
            messagebox.showerror("Ошибка", f"Произошла ошибка: {str(e)}", parent=self.root)
            app_logger.error(f"Непредвиденная ошибка при входе: {e}")
            audit_logger.error(f"Непредвиденная ошибка при входе: {e}")

    def create_main_window(self):
        self.clear_window()

        header = ttk.Label(self.root, text="### Менеджер Паролей ###", font=("Arial", 16, "bold"))
        header.pack(pady=20)

        # Загрузка иконок для кнопок
        self.load_button_icons()

        # Buttons Frame
        buttons_frame = ttk.Frame(self.root)
        buttons_frame.pack(pady=10)

        # Кнопка "Добавить Пароль"
        add_button = ttk.Button(buttons_frame, text="Добавить Пароль", width=20, command=self.add_password_window)
        if self.add_icon_photo:
            add_button.config(image=self.add_icon_photo, compound=tk.LEFT)
            add_button.image = self.add_icon_photo  # Сохраняем ссылку на изображение
        add_button.grid(row=0, column=0, padx=10, pady=10)

        # Кнопка "Просмотреть Пароли"
        view_button = ttk.Button(buttons_frame, text="Просмотреть Пароли", width=20, command=self.view_passwords_window)
        if self.view_icon_photo:
            view_button.config(image=self.view_icon_photo, compound=tk.LEFT)
            view_button.image = self.view_icon_photo  # Сохраняем ссылку на изображение
        view_button.grid(row=0, column=1, padx=10, pady=10)

        # Кнопка "Импорт из CSV"
        import_button = ttk.Button(buttons_frame, text="Импорт из CSV", width=20, command=self.import_from_csv_window)
        if self.import_icon_photo:
            import_button.config(image=self.import_icon_photo, compound=tk.LEFT)
            import_button.image = self.import_icon_photo  # Сохраняем ссылку на изображение
        import_button.grid(row=0, column=2, padx=10, pady=10)

        # Кнопка "Экспорт в CSV"
        export_button = ttk.Button(buttons_frame, text="Экспорт в CSV", width=20, command=self.export_to_csv_window)
        if self.export_icon_photo:
            export_button.config(image=self.export_icon_photo, compound=tk.LEFT)
            export_button.image = self.export_icon_photo  # Сохраняем ссылку на изображение
        export_button.grid(row=0, column=3, padx=10, pady=10)

        # Кнопка "Аудит Логов"
        audit_button = ttk.Button(buttons_frame, text="Аудит Логов", width=20, command=self.view_audit_logs)
        if self.audit_icon_photo:
            audit_button.config(image=self.audit_icon_photo, compound=tk.LEFT)
            audit_button.image = self.audit_icon_photo  # Сохраняем ссылку на изображение
        audit_button.grid(row=0, column=4, padx=10, pady=10)

        # Кнопка "Выход"
        exit_button = ttk.Button(buttons_frame, text="Выход", width=20, command=self.root.quit)
        if self.exit_icon_photo:
            exit_button.config(image=self.exit_icon_photo, compound=tk.LEFT)
            exit_button.image = self.exit_icon_photo  # Сохраняем ссылку на изображение
        exit_button.grid(row=0, column=5, padx=10, pady=10)

    def load_button_icons(self):
        # Загрузка иконок для кнопок
        try:
            add_icon = Image.open("add_icon.png")
            add_icon = add_icon.resize((20, 20), Image.ANTIALIAS)
            self.add_icon_photo = ImageTk.PhotoImage(add_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Добавить Пароль': {e}")
            self.add_icon_photo = None

        try:
            view_icon = Image.open("view_icon.png")
            view_icon = view_icon.resize((20, 20), Image.ANTIALIAS)
            self.view_icon_photo = ImageTk.PhotoImage(view_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Просмотреть Пароли': {e}")
            self.view_icon_photo = None

        try:
            import_icon = Image.open("import_icon.png")  # Предполагается, что есть иконка для импорта
            import_icon = import_icon.resize((20, 20), Image.ANTIALIAS)
            self.import_icon_photo = ImageTk.PhotoImage(import_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Импорт из CSV': {e}")
            self.import_icon_photo = None

        try:
            export_icon = Image.open("export_icon.png")  # Предполагается, что есть иконка для экспорта
            export_icon = export_icon.resize((20, 20), Image.ANTIALIAS)
            self.export_icon_photo = ImageTk.PhotoImage(export_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Экспорт в CSV': {e}")
            self.export_icon_photo = None

        try:
            audit_icon = Image.open("audit_icon.png")  # Предполагается, что есть иконка для аудита
            audit_icon = audit_icon.resize((20, 20), Image.ANTIALIAS)
            self.audit_icon_photo = ImageTk.PhotoImage(audit_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Аудит Логов': {e}")
            self.audit_icon_photo = None

        try:
            exit_icon = Image.open("exit_icon.png")
            exit_icon = exit_icon.resize((20, 20), Image.ANTIALIAS)
            self.exit_icon_photo = ImageTk.PhotoImage(exit_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Выход': {e}")
            self.exit_icon_photo = None

        try:
            delete_all_icon = Image.open("delete_all_icon.png")  # Предполагается, что есть иконка для удаления всех
            delete_all_icon = delete_all_icon.resize((20, 20), Image.ANTIALIAS)
            self.delete_all_icon_photo = ImageTk.PhotoImage(delete_all_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Удалить Все Пароли': {e}")
            self.delete_all_icon_photo = None

    def delete_all_passwords(self):
        """
        Обрабатывает удаление всех паролей после подтверждения пользователем.
        """
        confirm = messagebox.askyesno(
            "Подтверждение",
            "Вы уверены, что хотите удалить ВСЕ пароли? Это действие необратимо.",
            parent=self.root
        )
        if confirm:
            try:
                delete_all_passwords(self.data)
                save_data(FILE_PATH, self.master_password, self.data)
                messagebox.showinfo("Успех", "Все пароли успешно удалены.", parent=self.root)
                app_logger.info("Все пароли успешно удалены.")
                audit_logger.info("Пользователь удалил все пароли.")
                # Обновить все открытые Treeview
                self.refresh_all_treeviews()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось удалить все пароли: {str(e)}", parent=self.root)
                app_logger.error(f"Ошибка при удалении всех паролей: {e}")
                audit_logger.error("Ошибка при удалении всех паролей.")

    def view_passwords_window(self):
        view_win = tk.Toplevel(self.root)
        view_win.title("Просмотр Паролей")
        view_win.geometry("1000x600")  # Увеличена ширина для отображения новых полей
        view_win.resizable(False, False)

        frame = ttk.Frame(view_win, padding="20 20 20 20")
        frame.pack(expand=True, fill=tk.BOTH)

        header = ttk.Label(frame, text="Список всех паролей", style="Header.TLabel")
        header.pack(pady=(0, 20))

        # Настройка Treeview
        columns = ("Service", "Username", "Password", "Comments", "Tags")
        tree = ttk.Treeview(frame, columns=columns, show='headings', selectmode="browse")
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, width=180, anchor='center')
        tree.pack(fill=tk.BOTH, expand=True)

        # Полосы прокрутки
        scrollbar_y = ttk.Scrollbar(tree, orient=tk.VERTICAL, command=tree.yview)
        scrollbar_x = ttk.Scrollbar(tree, orient=tk.HORIZONTAL, command=tree.xview)
        tree.configure(yscroll=scrollbar_y.set, xscroll=scrollbar_x.set)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

        # Загрузка паролей в Treeview
        self.populate_treeview(tree)

        # Загрузка иконок для кнопок
        self.load_view_window_icons()

        # Кнопки для копирования, удаления, обновления, просмотра паролей и удаления всех паролей
        buttons_frame = ttk.Frame(frame)
        buttons_frame.pack(pady=10)

        def copy_password():
            selected_item = tree.focus()
            if selected_item:
                try:
                    password = self.get_password_by_id(selected_item)
                    pyperclip.copy(password)
                    messagebox.showinfo("Скопировано", "Пароль скопирован в буфер обмена.", parent=view_win)
                    app_logger.info(f"Пароль для ID='{selected_item}' скопирован в буфер обмена.")
                    audit_logger.info(f"Пароль для ID='{selected_item}' скопирован в буфер обмена.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось скопировать пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при копировании пароля: {e}")
                    audit_logger.error(f"Ошибка при копировании пароля для ID='{selected_item}'.")
            else:
                messagebox.showerror("Ошибка", "Выберите пароль для копирования.", parent=view_win)

        def delete_password():
            selected_item = tree.focus()
            if not selected_item:
                messagebox.showerror("Ошибка", "Выберите запись для удаления.", parent=view_win)
                return

            service = tree.item(selected_item)["values"][0]
            username = tree.item(selected_item)["values"][1]

            confirm = messagebox.askyesno("Подтверждение",
                                          f"Вы уверены, что хотите удалить пароль для сервиса '{service}' и пользователя '{username}'?",
                                          parent=view_win)
            if confirm:
                try:
                    success = delete_password_entry_by_id(self.data, selected_item)
                    if success:
                        save_data(FILE_PATH, self.master_password, self.data)
                        tree.delete(selected_item)
                        messagebox.showinfo("Успех", "Пароль удалён успешно.", parent=view_win)
                        app_logger.info(f"Пароль с ID='{selected_item}' удалён.")
                        audit_logger.info(f"Пароль с ID='{selected_item}' удалён.")
                    else:
                        messagebox.showerror("Ошибка", "Не удалось удалить пароль. Возможно, запись уже была удалена.",
                                             parent=view_win)
                        app_logger.warning(f"Не удалось удалить пароль с ID='{selected_item}'.")
                        audit_logger.warning(f"Не удалось удалить пароль с ID='{selected_item}'.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось удалить пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при удалении пароля: {e}")
                    audit_logger.error(f"Ошибка при удалении пароля с ID='{selected_item}'.")
    
        def refresh_treeview():
            self.populate_treeview(tree)
            messagebox.showinfo("Обновлено", "Список паролей обновлён.", parent=view_win)
            app_logger.info("Список паролей обновлён.")
            audit_logger.info("Список паролей обновлён.")
    
        def view_password():
            selected_item = tree.focus()
            if selected_item:
                try:
                    password = self.get_password_by_id(selected_item)
                    comments = self.get_comments_by_id(selected_item)
                    tags = self.get_tags_by_id(selected_item)
                    messagebox.showinfo("Пароль",
                                        f"Пароль: {password}\n\nКомментарии: {comments}\nТеги: {tags}",
                                        parent=view_win)
                    app_logger.info(f"Пароль для ID='{selected_item}' отображён пользователю.")
                    audit_logger.info(f"Пароль для ID='{selected_item}' отображён пользователю.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось получить пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при просмотре пароля: {e}")
                    audit_logger.error(f"Ошибка при просмотре пароля для ID='{selected_item}'.")
            else:
                messagebox.showerror("Ошибка", "Выберите пароль для просмотра.", parent=view_win)

        def delete_all_passwords_in_view():
            """
            Обрабатывает удаление всех паролей из окна просмотра паролей.
            """
            confirm = messagebox.askyesno(
                "Подтверждение",
                "Вы уверены, что хотите удалить ВСЕ пароли? Это действие необратимо.",
                parent=view_win
            )
            if confirm:
                try:
                    delete_all_passwords(self.data)
                    save_data(FILE_PATH, self.master_password, self.data)
                    messagebox.showinfo("Успех", "Все пароли успешно удалены.", parent=view_win)
                    app_logger.info("Все пароли успешно удалены через окно просмотра.")
                    audit_logger.info("Пользователь удалил все пароли через окно просмотра.")
                    # Очистить Treeview
                    for item in tree.get_children():
                        tree.delete(item)
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось удалить все пароли: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при удалении всех паролей через окно просмотра: {e}")
                    audit_logger.error("Ошибка при удалении всех паролей через окно просмотра.")

        copy_button = ttk.Button(buttons_frame, text="Скопировать Пароль", command=copy_password)
        if self.copy_icon_photo:
            copy_button.config(image=self.copy_icon_photo, compound=tk.LEFT)
            copy_button.image = self.copy_icon_photo  # Сохраняем ссылку на изображение
        copy_button.grid(row=0, column=0, padx=10)

        delete_button = ttk.Button(buttons_frame, text="Удалить Пароль", command=delete_password)
        if self.delete_icon_photo:
            delete_button.config(image=self.delete_icon_photo, compound=tk.LEFT)
            delete_button.image = self.delete_icon_photo  # Сохраняем ссылку на изображение
        delete_button.grid(row=0, column=1, padx=10)

        refresh_button = ttk.Button(buttons_frame, text="Обновить Список", command=refresh_treeview)
        if self.refresh_icon_photo:
            refresh_button.config(image=self.refresh_icon_photo, compound=tk.LEFT)
            refresh_button.image = self.refresh_icon_photo  # Сохраняем ссылку на изображение
        refresh_button.grid(row=0, column=2, padx=10)

        view_button = ttk.Button(buttons_frame, text="Просмотреть Пароль", command=view_password)
        if self.view_icon_photo:
            view_button.config(image=self.view_icon_photo, compound=tk.LEFT)
            view_button.image = self.view_icon_photo  # Сохраняем ссылку на изображение
        view_button.grid(row=0, column=3, padx=10)

        # Новая кнопка "Удалить Все Пароли" в окне просмотра
        delete_all_button = ttk.Button(buttons_frame, text="Удалить Все Пароли", width=20, command=delete_all_passwords_in_view)
        if self.delete_all_icon_photo:
            delete_all_button.config(image=self.delete_all_icon_photo, compound=tk.LEFT)
            delete_all_button.image = self.delete_all_icon_photo  # Сохраняем ссылку на изображение
        delete_all_button.grid(row=0, column=4, padx=10, pady=10)

    def load_view_window_icons(self):
        try:
            copy_icon = Image.open("copy_icon.png")
            copy_icon = copy_icon.resize((20, 20), Image.ANTIALIAS)
            self.copy_icon_photo = ImageTk.PhotoImage(copy_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Скопировать Пароль': {e}")
            self.copy_icon_photo = None

        try:
            delete_icon = Image.open("delete_icon.png")
            delete_icon = delete_icon.resize((20, 20), Image.ANTIALIAS)
            self.delete_icon_photo = ImageTk.PhotoImage(delete_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Удалить Пароль': {e}")
            self.delete_icon_photo = None

        try:
            refresh_icon = Image.open("refresh_icon.png")
            refresh_icon = refresh_icon.resize((20, 20), Image.ANTIALIAS)
            self.refresh_icon_photo = ImageTk.PhotoImage(refresh_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Обновить Список': {e}")
            self.refresh_icon_photo = None

        try:
            view_icon = Image.open("view_icon.png")
            view_icon = view_icon.resize((20, 20), Image.ANTIALIAS)
            self.view_icon_photo = ImageTk.PhotoImage(view_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Просмотреть Пароль': {e}")
            self.view_icon_photo = None

    def add_password_window(self):
        if self.add_password_window_open:
            # Если окно уже открыто, просто поднимаем его на передний план
            self.add_win.focus_force()
            return

        self.add_password_window_open = True  # Устанавливаем флаг

        add_win = tk.Toplevel(self.root)
        add_win.title("Добавить Пароль")
        add_win.geometry("500x600")  # Увеличена высота для размещения элементов
        add_win.resizable(False, False)

        # Обработка закрытия окна
        def on_close():
            self.add_password_window_open = False
            add_win.destroy()

        add_win.protocol("WM_DELETE_WINDOW", on_close)

        frame = ttk.Frame(add_win, padding="20 20 20 20")
        frame.pack(expand=True, fill=tk.BOTH)

        header = ttk.Label(frame, text="Добавление нового пароля", style="Header.TLabel")
        header.pack(pady=(0, 20))

        # URL
        url_label = ttk.Label(frame, text="URL:")
        url_label.pack(pady=(0, 5), anchor=tk.W)
        url_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
        url_entry.pack(pady=(0, 15), anchor=tk.W)

        # Имя пользователя с галочкой
        self.no_username_var = tk.IntVar()
        no_username_check = ttk.Checkbutton(
            frame,
            text="Без имени пользователя",
            variable=self.no_username_var,
            command=lambda: self.toggle_username_entry(username_entry)
        )
        no_username_check.pack(pady=(0, 5), anchor=tk.W)

        username_label = ttk.Label(frame, text="Имя пользователя:")
        username_label.pack(pady=(10, 5), anchor=tk.W)
        username_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
        username_entry.pack(pady=(0, 15), anchor=tk.W)

        # Пароль
        password_label = ttk.Label(frame, text="Пароль:")
        password_label.pack(pady=(0, 5), anchor=tk.W)
        password_entry = ttk.Entry(frame, width=40, show="*", font=("Arial", 12))
        password_entry.pack(pady=(0, 10), anchor=tk.W)

        # Индикатор силы пароля
        strength_label = ttk.Label(frame, text="Сила пароля:")
        strength_label.pack(anchor=tk.W)
        self.strength_var = tk.StringVar(value="Неизвестно")
        self.strength_indicator = ttk.Progressbar(frame, orient="horizontal", length=300, mode="determinate", style="red.Horizontal.TProgressbar")
        self.strength_indicator.pack(pady=(0, 10), anchor=tk.W)

        # Обновление индикатора силы при изменении пароля
        def update_strength(event=None):
            pwd = password_entry.get()
            is_strong, message = check_password_strength(pwd)
            if is_strong:
                self.strength_var.set("Надежный")
                self.strength_indicator['value'] = 100
                self.strength_indicator.configure(style="green.Horizontal.TProgressbar")
            else:
                self.strength_var.set("Слабый")
                self.strength_indicator['value'] = 25
                self.strength_indicator.configure(style="red.Horizontal.TProgressbar")

        # Настройка стилей для прогресс-бара
        style = ttk.Style()
        style.configure("red.Horizontal.TProgressbar", troughcolor="#2C3E50", background="#E74C3C")
        style.configure("green.Horizontal.TProgressbar", troughcolor="#2C3E50", background="#2ECC71")

        password_entry.bind("<KeyRelease>", update_strength)

        strength_display = ttk.Label(frame, textvariable=self.strength_var)
        strength_display.pack(anchor=tk.W)

        # Комментарии
        comments_label = ttk.Label(frame, text="Комментарии:")
        comments_label.pack(pady=(10, 5), anchor=tk.W)
        comments_entry = scrolledtext.ScrolledText(frame, wrap=tk.WORD, width=40, height=4, font=("Arial", 12))
        comments_entry.pack(pady=(0, 15), anchor=tk.W)

        # Теги
        tags_label = ttk.Label(frame, text="Теги (через запятую):")
        tags_label.pack(pady=(0, 5), anchor=tk.W)
        tags_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
        tags_entry.pack(pady=(0, 15), anchor=tk.W)

        # Кнопки: Показать/Скрыть и Генерация пароля
        buttons_frame = ttk.Frame(frame)
        buttons_frame.pack(pady=(0, 20), anchor=tk.W)

        def toggle_password():
            if password_entry.cget('show') == '':
                password_entry.config(show="*")
                toggle_button.config(text="Показать")
            else:
                password_entry.config(show='')
                toggle_button.config(text="Скрыть")

        toggle_button = ttk.Button(buttons_frame, text="Показать", width=12, command=toggle_password)
        toggle_button.grid(row=0, column=0, padx=(0, 10))

        def generate_and_set_password():
            generated = generate_password()
            password_entry.delete(0, tk.END)
            password_entry.insert(0, generated)
            app_logger.info("Сгенерирован новый пароль.")
            audit_logger.info("Сгенерирован новый пароль.")
            update_strength()

        generate_button = ttk.Button(buttons_frame, text="Сгенерировать Пароль", command=generate_and_set_password)
        generate_button.grid(row=0, column=1, padx=(0, 10))

        # Загрузка иконки для кнопки "Сохранить"
        try:
            save_icon = Image.open("add_icon.png")
            save_icon = save_icon.resize((20, 20), Image.ANTIALIAS)
            self.save_icon_photo = ImageTk.PhotoImage(save_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Сохранить': {e}")
            self.save_icon_photo = None

        # Кнопка сохранения
        save_button = ttk.Button(frame, text="Сохранить", command=lambda: self.save_password(
            url_entry.get().strip(),
            username_entry.get().strip(),
            password_entry.get().strip(),
            comments_entry.get("1.0", tk.END).strip(),
            tags_entry.get().strip(),
            add_win
        ))
        if self.save_icon_photo:
            save_button.config(image=self.save_icon_photo, compound=tk.LEFT)
            save_button.image = self.save_icon_photo  # Сохраняем ссылку на изображение
        save_button.pack(pady=10)

    def toggle_username_entry(self, username_entry):
        """
        Отключает или включает поле ввода имени пользователя в зависимости от состояния галочки.
        """
        if self.no_username_var.get():
            username_entry.config(state='disabled')
            username_entry.delete(0, tk.END)
        else:
            username_entry.config(state='normal')

    def save_password(self, url, username, password, comments, tags, window):
        if not url or not password:
            messagebox.showerror("Ошибка", "Поля 'URL' и 'Пароль' обязательно для заполнения.", parent=window)
            return

        # Проверка силы пароля
        is_strong, message = check_password_strength(password)
        if not is_strong:
            messagebox.showerror("Слабый пароль", f"Пароль не соответствует требованиям безопасности:\n{message}", parent=window)
            audit_logger.warning("Попытка добавить пароль, не соответствующий требованиям безопасности.")
            return

        if not username and not self.no_username_var.get():
            messagebox.showerror("Ошибка", "Поле 'Имя пользователя' обязательно для заполнения или отметьте 'Без имени пользователя'.", parent=window)
            return

        try:
            add_password(self.data, url, username, password, comments, tags)
            save_data(FILE_PATH, self.master_password, self.data)
            messagebox.showinfo("Успех", "Пароль добавлен успешно.", parent=window)
            app_logger.info(f"Добавлен новый пароль для URL '{url}'.")
            audit_logger.info(f"Добавлен новый пароль для URL '{url}'.")
            window.destroy()
            self.add_password_window_open = False  # Сбрасываем флаг
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось сохранить пароль: {str(e)}", parent=window)
            app_logger.error(f"Ошибка при сохранении пароля: {e}")
            audit_logger.error(f"Ошибка при сохранении пароля для URL '{url}'.")

    def view_passwords_window(self):
        view_win = tk.Toplevel(self.root)
        view_win.title("Просмотр Паролей")
        view_win.geometry("1000x600")  # Увеличена ширина для отображения новых полей
        view_win.resizable(False, False)

        frame = ttk.Frame(view_win, padding="20 20 20 20")
        frame.pack(expand=True, fill=tk.BOTH)

        header = ttk.Label(frame, text="Список всех паролей", style="Header.TLabel")
        header.pack(pady=(0, 20))

        # Настройка Treeview
        columns = ("Service", "Username", "Password", "Comments", "Tags")
        tree = ttk.Treeview(frame, columns=columns, show='headings', selectmode="browse")
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, width=180, anchor='center')
        tree.pack(fill=tk.BOTH, expand=True)

        # Полосы прокрутки
        scrollbar_y = ttk.Scrollbar(tree, orient=tk.VERTICAL, command=tree.yview)
        scrollbar_x = ttk.Scrollbar(tree, orient=tk.HORIZONTAL, command=tree.xview)
        tree.configure(yscroll=scrollbar_y.set, xscroll=scrollbar_x.set)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

        # Загрузка паролей в Treeview
        self.populate_treeview(tree)

        # Загрузка иконок для кнопок
        self.load_view_window_icons()

        # Кнопки для копирования, удаления, обновления, просмотра паролей и удаления всех паролей
        buttons_frame = ttk.Frame(frame)
        buttons_frame.pack(pady=10)

        def copy_password():
            selected_item = tree.focus()
            if selected_item:
                try:
                    password = self.get_password_by_id(selected_item)
                    pyperclip.copy(password)
                    messagebox.showinfo("Скопировано", "Пароль скопирован в буфер обмена.", parent=view_win)
                    app_logger.info(f"Пароль для ID='{selected_item}' скопирован в буфер обмена.")
                    audit_logger.info(f"Пароль для ID='{selected_item}' скопирован в буфер обмена.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось скопировать пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при копировании пароля: {e}")
                    audit_logger.error(f"Ошибка при копировании пароля для ID='{selected_item}'.")
            else:
                messagebox.showerror("Ошибка", "Выберите пароль для копирования.", parent=view_win)

        def delete_password():
            selected_item = tree.focus()
            if not selected_item:
                messagebox.showerror("Ошибка", "Выберите запись для удаления.", parent=view_win)
                return

            service = tree.item(selected_item)["values"][0]
            username = tree.item(selected_item)["values"][1]

            confirm = messagebox.askyesno("Подтверждение",
                                          f"Вы уверены, что хотите удалить пароль для сервиса '{service}' и пользователя '{username}'?",
                                          parent=view_win)
            if confirm:
                try:
                    success = delete_password_entry_by_id(self.data, selected_item)
                    if success:
                        save_data(FILE_PATH, self.master_password, self.data)
                        tree.delete(selected_item)
                        messagebox.showinfo("Успех", "Пароль удалён успешно.", parent=view_win)
                        app_logger.info(f"Пароль с ID='{selected_item}' удалён.")
                        audit_logger.info(f"Пароль с ID='{selected_item}' удалён.")
                    else:
                        messagebox.showerror("Ошибка", "Не удалось удалить пароль. Возможно, запись уже была удалена.",
                                             parent=view_win)
                        app_logger.warning(f"Не удалось удалить пароль с ID='{selected_item}'.")
                        audit_logger.warning(f"Не удалось удалить пароль с ID='{selected_item}'.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось удалить пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при удалении пароля: {e}")
                    audit_logger.error(f"Ошибка при удалении пароля с ID='{selected_item}'.")
    
        def refresh_treeview():
            self.populate_treeview(tree)
            messagebox.showinfo("Обновлено", "Список паролей обновлён.", parent=view_win)
            app_logger.info("Список паролей обновлён.")
            audit_logger.info("Список паролей обновлён.")
    
        def view_password():
            selected_item = tree.focus()
            if selected_item:
                try:
                    password = self.get_password_by_id(selected_item)
                    comments = self.get_comments_by_id(selected_item)
                    tags = self.get_tags_by_id(selected_item)
                    messagebox.showinfo("Пароль",
                                        f"Пароль: {password}\n\nКомментарии: {comments}\nТеги: {tags}",
                                        parent=view_win)
                    app_logger.info(f"Пароль для ID='{selected_item}' отображён пользователю.")
                    audit_logger.info(f"Пароль для ID='{selected_item}' отображён пользователю.")
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось получить пароль: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при просмотре пароля: {e}")
                    audit_logger.error(f"Ошибка при просмотре пароля для ID='{selected_item}'.")
            else:
                messagebox.showerror("Ошибка", "Выберите пароль для просмотра.", parent=view_win)

        def delete_all_passwords_in_view():
            """
            Обрабатывает удаление всех паролей из окна просмотра паролей.
            """
            confirm = messagebox.askyesno(
                "Подтверждение",
                "Вы уверены, что хотите удалить ВСЕ пароли? Это действие необратимо.",
                parent=view_win
            )
            if confirm:
                try:
                    delete_all_passwords(self.data)
                    save_data(FILE_PATH, self.master_password, self.data)
                    messagebox.showinfo("Успех", "Все пароли успешно удалены.", parent=view_win)
                    app_logger.info("Все пароли успешно удалены через окно просмотра.")
                    audit_logger.info("Пользователь удалил все пароли через окно просмотра.")
                    # Очистить Treeview
                    for item in tree.get_children():
                        tree.delete(item)
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось удалить все пароли: {str(e)}", parent=view_win)
                    app_logger.error(f"Ошибка при удалении всех паролей через окно просмотра: {e}")
                    audit_logger.error("Ошибка при удалении всех паролей через окно просмотра.")

        copy_button = ttk.Button(buttons_frame, text="Скопировать Пароль", command=copy_password)
        if self.copy_icon_photo:
            copy_button.config(image=self.copy_icon_photo, compound=tk.LEFT)
            copy_button.image = self.copy_icon_photo  # Сохраняем ссылку на изображение
        copy_button.grid(row=0, column=0, padx=10)

        delete_button = ttk.Button(buttons_frame, text="Удалить Пароль", command=delete_password)
        if self.delete_icon_photo:
            delete_button.config(image=self.delete_icon_photo, compound=tk.LEFT)
            delete_button.image = self.delete_icon_photo  # Сохраняем ссылку на изображение
        delete_button.grid(row=0, column=1, padx=10)

        refresh_button = ttk.Button(buttons_frame, text="Обновить Список", command=refresh_treeview)
        if self.refresh_icon_photo:
            refresh_button.config(image=self.refresh_icon_photo, compound=tk.LEFT)
            refresh_button.image = self.refresh_icon_photo  # Сохраняем ссылку на изображение
        refresh_button.grid(row=0, column=2, padx=10)

        view_button = ttk.Button(buttons_frame, text="Просмотреть Пароль", command=view_password)
        if self.view_icon_photo:
            view_button.config(image=self.view_icon_photo, compound=tk.LEFT)
            view_button.image = self.view_icon_photo  # Сохраняем ссылку на изображение
        view_button.grid(row=0, column=3, padx=10)

        # Новая кнопка "Удалить Все Пароли" в окне просмотра паролей
        delete_all_button = ttk.Button(buttons_frame, text="Удалить Все Пароли", width=20, command=delete_all_passwords_in_view)
        if self.delete_all_icon_photo:
            delete_all_button.config(image=self.delete_all_icon_photo, compound=tk.LEFT)
            delete_all_button.image = self.delete_all_icon_photo  # Сохраняем ссылку на изображение
        delete_all_button.grid(row=0, column=4, padx=10, pady=10)

    def load_view_window_icons(self):
        try:
            copy_icon = Image.open("copy_icon.png")
            copy_icon = copy_icon.resize((20, 20), Image.ANTIALIAS)
            self.copy_icon_photo = ImageTk.PhotoImage(copy_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Скопировать Пароль': {e}")
            self.copy_icon_photo = None

        try:
            delete_icon = Image.open("delete_icon.png")
            delete_icon = delete_icon.resize((20, 20), Image.ANTIALIAS)
            self.delete_icon_photo = ImageTk.PhotoImage(delete_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Удалить Пароль': {e}")
            self.delete_icon_photo = None

        try:
            refresh_icon = Image.open("refresh_icon.png")
            refresh_icon = refresh_icon.resize((20, 20), Image.ANTIALIAS)
            self.refresh_icon_photo = ImageTk.PhotoImage(refresh_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Обновить Список': {e}")
            self.refresh_icon_photo = None

        try:
            view_icon = Image.open("view_icon.png")
            view_icon = view_icon.resize((20, 20), Image.ANTIALIAS)
            self.view_icon_photo = ImageTk.PhotoImage(view_icon)
        except Exception as e:
            app_logger.error(f"Не удалось загрузить иконку для кнопки 'Просмотреть Пароль': {e}")
            self.view_icon_photo = None

    def delete_all_passwords_in_view(self):
        """
        Обрабатывает удаление всех паролей из окна просмотра паролей.
        """
        confirm = messagebox.askyesno(
            "Подтверждение",
            "Вы уверены, что хотите удалить ВСЕ пароли? Это действие необратимо.",
            parent=self.root
        )
        if confirm:
            try:
                delete_all_passwords(self.data)
                save_data(FILE_PATH, self.master_password, self.data)
                messagebox.showinfo("Успех", "Все пароли успешно удалены.", parent=self.root)
                app_logger.info("Все пароли успешно удалены через окно просмотра.")
                audit_logger.info("Пользователь удалил все пароли через окно просмотра.")
                # Очистить Treeview
                # Здесь предполагается, что дерево уже пусто после удаления всех паролей
                # Для явной очистки можно пройтись по всем Treeview и удалить элементы
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось удалить все пароли: {str(e)}", parent=self.root)
                app_logger.error(f"Ошибка при удалении всех паролей через окно просмотра: {e}")
                audit_logger.error("Ошибка при удалении всех паролей через окно просмотра.")

    def populate_treeview(self, tree):
        """
        Заполняет Treeview паролями из данных, скрывая пароли по умолчанию.
        """
        # Очистка текущих данных
        for item in tree.get_children():
            tree.delete(item)

        # Загрузка данных
        try:
            passwords = get_all_passwords(self.data)
            for url, creds in passwords.items():
                for cred in creds:
                    if 'id' not in cred:
                        continue
                    display_username = cred["username"] if cred["username"] else "—"
                    masked_password = '*' * len(cred["password"])
                    comments = cred.get('comments', '')
                    tags = cred.get('tags', '')
                    tree.insert("", tk.END, iid=cred["id"], values=(url, display_username, masked_password, comments, tags))
            app_logger.info("Treeview успешно заполнен данными паролей.")
            audit_logger.info("Treeview заполнен данными паролей.")
        except KeyError:
            messagebox.showerror("Ошибка", "Формат данных некорректен.", parent=self.root)
            app_logger.error("Формат данных некорректен при заполнении Treeview.")
            audit_logger.error("Формат данных некорректен при заполнении Treeview.")

    def refresh_all_treeviews(self):
        """
        Обновляет все открытые окна с Treeview.
        """
        for widget in self.root.winfo_children():
            if isinstance(widget, tk.Toplevel):
                for child in widget.winfo_children():
                    if isinstance(child, ttk.Frame):
                        for subchild in child.winfo_children():
                            if isinstance(subchild, ttk.Treeview):
                                self.populate_treeview(subchild)

    def clear_window(self):
        for widget in self.root.winfo_children():
            widget.destroy()


def main():
    root = tk.Tk()
    app = PasswordManagerApp(root)
    root.mainloop()


if __name__ == "__main__":  # Исправлено условие запуска
    main()

password_manager.py:

# password_manager.py

import os
import json
import base64
import logging
from logging.handlers import RotatingFileHandler
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.fernet import Fernet, InvalidToken
import secrets
import string
import uuid  # Для генерации уникальных идентификаторов
import re  # Для проверки пароля
import csv  # Для импорта и экспорта CSV

# Настройка общих логов приложения
app_logger = logging.getLogger('app_logger')
app_logger.setLevel(logging.INFO)
app_handler = RotatingFileHandler('password_manager.log', maxBytes=1000000, backupCount=5)
app_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
app_handler.setFormatter(app_formatter)
app_logger.addHandler(app_handler)

# Настройка логов аудита
audit_logger = logging.getLogger('audit_logger')
audit_logger.setLevel(logging.INFO)
audit_handler = RotatingFileHandler('audit.log', maxBytes=1000000, backupCount=5)
audit_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
audit_handler.setFormatter(audit_formatter)
audit_logger.addHandler(audit_handler)


def derive_key(password: str, salt: bytes) -> bytes:
    """
    Генерирует криптографический ключ из пароля и соли.
    """
    kdf_instance = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100_000,
        backend=default_backend()
    )
    return base64.urlsafe_b64encode(kdf_instance.derive(password.encode()))


def load_data(file_path: str, password: str) -> dict:
    """
    Загружает и дешифрует данные из файла.
    Если файл не существует или повреждён, создаёт новый с пустыми данными.
    """
    if not os.path.exists(file_path):
        # Генерируем новую соль и создаём структуру данных
        new_salt = base64.urlsafe_b64encode(os.urandom(16)).decode()
        new_data = {"salt": new_salt, "data": {}}
        app_logger.info("Файл данных не найден. Создаётся новый файл.")
        audit_logger.info("Создан новый файл данных.")
        # Сохраняем новый файл
        save_data(file_path, password, new_data)
        return new_data

    with open(file_path, 'rb') as file:
        file_content = file.read()
        try:
            decoded = json.loads(file_content)
            app_logger.info("Файл данных успешно загружен.")
        except json.JSONDecodeError:
            # Если файл повреждён, создаём новый
            app_logger.error("Файл данных повреждён или имеет неверный формат.")
            audit_logger.warning("Попытка загрузки повреждённого файла данных.")
            raise ValueError("Файл данных повреждён или имеет неверный формат.")

        # Проверка наличия необходимых ключей
        if 'salt' not in decoded or 'data' not in decoded:
            app_logger.error("Файл данных имеет неверную структуру.")
            audit_logger.warning("Файл данных имеет неверную структуру.")
            raise ValueError("Файл данных имеет неверную структуру.")

        try:
            salt = base64.urlsafe_b64decode(decoded['salt'])
        except Exception as e:
            app_logger.error(f"Ошибка при декодировании соли: {e}")
            audit_logger.error("Ошибка при декодировании соли в файле данных.")
            raise ValueError("Некорректная соль в файле данных.")

        key = derive_key(password, salt)
        fernet = Fernet(key)
        try:
            decrypted_data = fernet.decrypt(decoded['data'].encode())
            decrypted_json = json.loads(decrypted_data)
            app_logger.info("Данные успешно дешифрованы.")
            audit_logger.info("Данные успешно дешифрованы.")
            return {"salt": decoded['salt'], "data": decrypted_json}
        except InvalidToken:
            app_logger.warning("Неверный мастер-пароль!")
            audit_logger.warning("Попытка входа с неверным мастер-паролем.")
            raise ValueError("Неверный мастер-пароль!")
        except Exception as e:
            app_logger.error(f"Не удалось дешифровать данные: {e}")
            audit_logger.error("Не удалось дешифровать данные.")
            raise ValueError("Не удалось дешифровать данные.")


def save_data(file_path: str, password: str, data: dict):
    """
    Шифрует и сохраняет данные в файл.
    """
    try:
        salt = base64.urlsafe_b64decode(data['salt'])
    except Exception as e:
        app_logger.error(f"Ошибка при декодировании соли для сохранения: {e}")
        audit_logger.error("Ошибка при декодировании соли для сохранения данных.")
        raise ValueError("Некорректная соль в данных.")

    key = derive_key(password, salt)
    fernet = Fernet(key)
    try:
        encrypted_data = fernet.encrypt(json.dumps(data["data"]).encode())
    except Exception as e:
        app_logger.error(f"Ошибка при шифровании данных: {e}")
        audit_logger.error("Не удалось зашифровать данные.")
        raise ValueError("Не удалось зашифровать данные.")

    to_save = {
        "salt": data['salt'],
        "data": encrypted_data.decode()
    }
    try:
        with open(file_path, 'w') as file:
            json.dump(to_save, file)
        app_logger.info("Данные успешно сохранены.")
        audit_logger.info("Данные успешно сохранены.")
    except Exception as e:
        app_logger.error(f"Ошибка при сохранении файла данных: {e}")
        audit_logger.error("Не удалось сохранить файл данных.")
        raise ValueError("Не удалось сохранить файл данных.")


def generate_password(length: int = 16) -> str:
    """
    Генерирует случайный сильный пароль.
    Ограничивает набор символов для повышения совместимости.
    """
    characters = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{}|;:,.<>?/"
    return ''.join(secrets.choice(characters) for _ in range(length))


def add_password(data: dict, url: str, username: str, password: str, comments: str = "", tags: str = ""):
    """
    Добавляет новый пароль в хранилище.
    """
    if url not in data['data']:
        data['data'][url] = []

    new_entry = {
        "id": str(uuid.uuid4()),  # Генерация уникального ID
        "username": username,
        "password": password,
        "comments": comments,
        "tags": tags
    }

    data['data'][url].append(new_entry)
    app_logger.info(f"Добавлена запись: URL='{url}', Username='{username}', Tags='{tags}'.")
    audit_logger.info(f"Добавлена запись: URL='{url}', Username='{username}', Tags='{tags}'.")


def get_all_passwords(data: dict) -> dict:
    """
    Возвращает все хранящиеся пароли.
    """
    return data['data']


def delete_password_entry_by_id(data: dict, entry_id: str) -> bool:
    """
    Удаляет конкретную запись пароля из хранилища по ID.
    Возвращает True, если успешно удалено, иначе False.
    """
    for url, creds in list(data['data'].items()):
        for cred in creds:
            if cred.get('id') == entry_id:
                creds.remove(cred)
                app_logger.info(f"Удалена запись ID='{entry_id}' для URL='{url}', пользователя '{cred['username']}'.")
                audit_logger.info(f"Удалена запись ID='{entry_id}' для URL='{url}', пользователя '{cred['username']}'.")
                if not creds:
                    del data['data'][url]
                    app_logger.info(f"Удалён URL '{url}', так как у него больше нет записей.")
                    audit_logger.info(f"Удалён URL '{url}', так как у него больше нет записей.")
                return True
    app_logger.warning(f"Запись ID='{entry_id}' не найдена.")
    audit_logger.warning(f"Попытка удаления несуществующей записи ID='{entry_id}'.")
    return False


def delete_all_passwords(data: dict):
    """
    Удаляет все записи паролей из хранилища.
    """
    data['data'] = {}
    app_logger.info("Удалены все записи паролей.")
    audit_logger.info("Все записи паролей удалены пользователем.")


def check_password_strength(password: str) -> (bool, str):
    """
    Проверяет силу пароля на основе заданных критериев.
    Возвращает кортеж: (соответствует ли минимальным требованиям, сообщение о состоянии).
    """
    length_error = len(password) < 8
    digit_error = re.search(r"\d", password) is None
    uppercase_error = re.search(r"[A-Z]", password) is None
    lowercase_error = re.search(r"[a-z]", password) is None
    symbol_error = re.search(r"[!@#$%^&*(),.?\":{}|<>]", password) is None

    errors = []
    if length_error:
        errors.append("Пароль должен содержать не менее 8 символов.")
    if digit_error:
        errors.append("Пароль должен содержать хотя бы одну цифру.")
    if uppercase_error:
        errors.append("Пароль должен содержать хотя бы одну заглавную букву.")
    if lowercase_error:
        errors.append("Пароль должен содержать хотя бы одну строчную букву.")
    if symbol_error:
        errors.append("Пароль должен содержать хотя бы один специальный символ (!@#$%^&*(),.?\":{}|<>).")

    if errors:
        return False, "\n".join(errors)

    return True, "Пароль соответствует требованиям."


def export_to_csv(file_path: str, data: dict):
    """
    Экспортирует все записи паролей в CSV-файл.
    Формат: url,username,password,comment,tags
    """
    try:
        with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
            fieldnames = ['url', 'username', 'password', 'comment', 'tags']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

            writer.writeheader()
            for url, creds in data['data'].items():
                for cred in creds:
                    writer.writerow({
                        'url': url,
                        'username': cred.get('username', ''),
                        'password': cred.get('password', ''),
                        'comment': cred.get('comments', ''),
                        'tags': cred.get('tags', '')
                    })
        app_logger.info(f"Данные успешно экспортированы в CSV-файл: {file_path}")
        audit_logger.info(f"Данные экспортированы в CSV-файл: {file_path}")
    except Exception as e:
        app_logger.error(f"Ошибка при экспорте данных в CSV: {e}")
        audit_logger.error(f"Ошибка при экспорте данных в CSV-файл: {file_path}")
        raise e


def import_from_csv(file_path: str, data: dict):
    """
    Импортирует записи паролей из CSV-файла.
    Формат CSV: url,username,password,comment,tags
    """
    try:
        with open(file_path, 'r', newline='', encoding='utf-8') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                url = row.get('url', '').strip()
                username = row.get('username', '').strip()
                password = row.get('password', '').strip()
                comments = row.get('comment', '').strip()
                tags = row.get('tags', '').strip()
                # Проверка наличия обязательных полей
                if not url or not password:
                    app_logger.warning(f"Пропущена запись с неполными данными: {row}")
                    audit_logger.warning(f"Пропущена запись с неполными данными при импорте из CSV: {row}")
                    continue
                # Проверка существования URL и уникальности записи
                if url not in data['data']:
                    data['data'][url] = []
                # Избежание дубликатов по url, username и password
                duplicate = False
                for cred in data['data'][url]:
                    if cred['username'] == username and cred['password'] == password:
                        duplicate = True
                        break
                if not duplicate:
                    new_entry = {
                        "id": str(uuid.uuid4()),
                        "username": username,
                        "password": password,
                        "comments": comments,
                        "tags": tags
                    }
                    data['data'][url].append(new_entry)
                    app_logger.info(f"Импортирована запись: URL='{url}', Username='{username}', Tags='{tags}'.")
                    audit_logger.info(f"Импортирована запись: URL='{url}', Username='{username}', Tags='{tags}'.")
                else:
                    app_logger.info(f"Пропущена дублирующая запись: URL='{url}', Username='{username}'.")
                    audit_logger.info(f"Пропущена дублирующая запись при импорте: URL='{url}', Username='{username}'.")
        app_logger.info(f"Данные успешно импортированы из CSV-файла: {file_path}")
        audit_logger.info(f"Данные импортированы из CSV-файла: {file_path}")
    except FileNotFoundError:
        app_logger.error(f"CSV-файл не найден: {file_path}")
        audit_logger.error(f"Попытка импорта из несуществующего CSV-файла: {file_path}")
        raise FileNotFoundError(f"CSV-файл не найден: {file_path}")
    except Exception as e:
        app_logger.error(f"Ошибка при импорте данных из CSV: {e}")
        audit_logger.error(f"Ошибка при импорте данных из CSV-файла: {file_path}")
        raise e


можешь разделить на несколько файлов, чтобы проще было управлять файлами