Создаю менеджер паролей
два файла в папке password_manager: gui.py и main.py

gui.py:
# gui.py

import tkinter as tk
from tkinter import messagebox, ttk
from PIL import Image, ImageTk
import pyperclip
from password_manager import (
    load_data, save_data, add_password, get_all_passwords,
    delete_password_entry_by_id, generate_password
)

FILE_PATH = 'data.enc'


class PasswordManagerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Менеджер Паролей")
        self.root.geometry("800x600")
        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')])

    def load_background(self):
        try:
            # Загрузите ваше фоновое изображение
            self.background_image = Image.open("background.png")
            self.background_image = self.background_image.resize((800, 600), 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:
            print(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=30, 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:
            print(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()

    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
            self.create_main_window()
        except ValueError as ve:
            messagebox.showerror("Ошибка", str(ve), parent=self.root)
        except Exception as e:
            messagebox.showerror("Ошибка", f"Произошла ошибка: {str(e)}", parent=self.root)

    def create_main_window(self):
        self.clear_window()

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

        # Загрузка иконок для кнопок
        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:
            print(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:
            print(f"Не удалось загрузить иконку для кнопки 'Просмотреть Пароли': {e}")
            self.view_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:
            print(f"Не удалось загрузить иконку для кнопки 'Выход': {e}")
            self.exit_icon_photo = None

        # 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)

        # Кнопка "Выход"
        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=2, padx=10, pady=10)

    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("500x450")
        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))

        # Название сервиса
        service_label = ttk.Label(frame, text="Название сервиса:")
        service_label.pack(pady=(0, 5), anchor=tk.W)
        service_entry = ttk.Entry(frame, width=40, font=("Arial", 12))
        service_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)

        # Кнопки: Показать/Скрыть и Генерация пароля
        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)

        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:
            print(f"Не удалось загрузить иконку для кнопки 'Сохранить': {e}")
            self.save_icon_photo = None

        # Кнопка сохранения
        save_button = ttk.Button(frame, text="Сохранить", command=lambda: self.save_password(
            service_entry.get().strip(),
            username_entry.get().strip(),
            password_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, service, username, password, window):
        if not service or not password:
            messagebox.showerror("Ошибка", "Поля 'Название сервиса' и 'Пароль' обязательно для заполнения.", parent=window)
            return

        if self.no_username_var.get():
            username = ""  # Можно установить None или оставить как пустую строку

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

    def view_passwords_window(self):
        view_win = tk.Toplevel(self.root)
        view_win.title("Просмотр Паролей")
        view_win.geometry("800x500")
        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")
        tree = ttk.Treeview(frame, columns=columns, show='headings', selectmode="browse")
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, width=250, 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)

        # Загрузка иконок для кнопок
        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:
            print(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:
            print(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:
            print(f"Не удалось загрузить иконку для кнопки 'Обновить Список': {e}")
            self.refresh_icon_photo = None

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

        def copy_password():
            selected_item = tree.focus()
            if selected_item:
                password = tree.item(selected_item)["values"][2]
                pyperclip.copy(password)
                messagebox.showinfo("Скопировано", "Пароль скопирован в буфер обмена.", parent=view_win)
            else:
                messagebox.showerror("Ошибка", "Выберите пароль для копирования.", parent=view_win)

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

            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)
                    else:
                        messagebox.showerror("Ошибка", "Не удалось удалить пароль. Возможно, запись уже была удалена.",
                                             parent=view_win)
                except Exception as e:
                    messagebox.showerror("Ошибка", f"Не удалось удалить пароль: {str(e)}", parent=view_win)

        def refresh_treeview():
            self.populate_treeview(tree)
            messagebox.showinfo("Обновлено", "Список паролей обновлён.", parent=view_win)

        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)

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

        # Загрузка данных
        try:
            passwords = get_all_passwords(self.data)
            for service, creds in passwords.items():
                for cred in creds:
                    # Убедитесь, что каждая запись имеет уникальный 'id'
                    if 'id' not in cred:
                        continue  # Пропуск записей без 'id'
                    # Для отображения отсутствующего имени пользователя используем дефолтное значение
                    display_username = cred["username"] if cred["username"] else "—"
                    tree.insert("", tk.END, iid=cred["id"], values=(service, display_username, cred["password"]))
        except KeyError:
            messagebox.showerror("Ошибка", "Формат данных некорректен.", parent=self.root)

    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: