import tkinter as tk from tkinter import ttk, messagebox, scrolledtext, filedialog import json import base64 from cryptography.fernet import Fernet import os import pyperclip from datetime import datetime import getpass import platform import random import string from tkinter import font as tkfont import time # Установите библиотеки если их нет: # pip install cryptography pyperclip class ModernPasswordManager: def __init__(self, root): self.root = root self.root.title("🔐 SecurePass Manager") self.root.geometry("1200x750") # Устанавливаем иконку (если есть) try: self.root.iconbitmap('icon.ico') # Можно создать иконку except: pass # Центрирование окна self.center_window() # Современная цветовая схема self.colors = { 'primary': '#2A2D43', # Темно-синий 'secondary': '#1E1F29', # Более темный фон 'accent': '#4A6FA5', # Голубой акцент 'success': '#50C878', # Изумрудный 'warning': '#FF6B6B', # Красный/коралловый 'text': '#E8EAED', # Светлый текст 'entry_bg': '#3C3F58', # Фон полей ввода 'card_bg': '#34374C', # Фон карточек 'hover': '#5A6FA5', # Цвет при наведении 'gradient_start': '#667eea', # Для градиентов 'gradient_end': '#764ba2' } # Загружаем шрифты self.load_fonts() # Настройка стилей self.setup_styles() # Получаем путь к Документам self.documents_path = self.get_documents_path() # Инициализация файлов self.data_dir = os.path.join(self.documents_path, "SecurePassManager") os.makedirs(self.data_dir, exist_ok=True) self.filename = os.path.join(self.data_dir, "passwords.enc") self.keyfile = os.path.join(self.data_dir, "master.key") self.backup_dir = os.path.join(self.data_dir, "backups") os.makedirs(self.backup_dir, exist_ok=True) # Загрузка или создание ключа self.key = self.load_or_create_key() if self.key: self.cipher = Fernet(self.key) self.passwords = self.load_passwords() else: self.show_error_screen("Не удалось инициализировать систему шифрования") return # Создание интерфейса self.create_main_layout() self.update_service_list() # Запуск анимаций self.root.after(100, self.animate_welcome) def center_window(self): """Центрирует окно на экране""" self.root.update_idletasks() width = self.root.winfo_width() height = self.root.winfo_height() x = (self.root.winfo_screenwidth() // 2) - (width // 2) y = (self.root.winfo_screenheight() // 2) - (height // 2) self.root.geometry(f'{width}x{height}+{x}+{y}') def load_fonts(self): """Загружаем шрифты""" self.title_font = ("Segoe UI", 24, "bold") self.heading_font = ("Segoe UI", 14, "bold") self.body_font = ("Segoe UI", 10) self.mono_font = ("Consolas", 10) def setup_styles(self): """Настройка стилей для виджетов""" style = ttk.Style() style.theme_use('clam') # Настраиваем стили style.configure('Modern.TButton', font=self.body_font, borderwidth=0, relief='flat', padding=10) style.configure('Card.TFrame', background=self.colors['card_bg'], relief='flat', borderwidth=0) style.configure('Primary.TButton', font=self.body_font, background=self.colors['accent'], foreground=self.colors['text'], borderwidth=0) def get_documents_path(self): """Получает путь к папке Документы""" system = platform.system() if system == "Windows": import ctypes.wintypes CSIDL_PERSONAL = 5 SHGFP_TYPE_CURRENT = 0 buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) ctypes.windll.shell32.SHGetFolderPathW(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, buf) return buf.value elif system == "Darwin": return os.path.expanduser("~/Documents") else: return os.path.expanduser("~/Documents") def load_or_create_key(self): """Загружает или создает ключ шифрования""" if os.path.exists(self.keyfile): try: with open(self.keyfile, 'rb') as f: return f.read() except: return None else: try: key = Fernet.generate_key() with open(self.keyfile, 'wb') as f: f.write(key) self.show_welcome_message() return key except: return None def show_welcome_message(self): """Показывает приветственное сообщение""" welcome_window = tk.Toplevel(self.root) welcome_window.title("Добро пожаловать!") welcome_window.geometry("500x400") welcome_window.configure(bg=self.colors['primary']) welcome_window.resizable(False, False) # Центрируем welcome_window.update_idletasks() width = welcome_window.winfo_width() height = welcome_window.winfo_height() x = (welcome_window.winfo_screenwidth() // 2) - (width // 2) y = (welcome_window.winfo_screenheight() // 2) - (height // 2) welcome_window.geometry(f'{width}x{height}+{x}+{y}') # Содержимое frame = tk.Frame(welcome_window, bg=self.colors['primary']) frame.pack(expand=True, fill='both', padx=40, pady=40) tk.Label(frame, text="🔐", font=("Segoe UI", 48), bg=self.colors['primary'], fg=self.colors['accent']).pack(pady=(0, 20)) tk.Label(frame, text="SecurePass Manager", font=self.title_font, bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 10)) message = ("Добро пожаловать в SecurePass Manager!\n\n" "• Все ваши пароли защищены современным шифрованием\n" "• Ключ шифрования сохранен в безопасном месте\n" "• Данные хранятся локально на вашем компьютере\n\n" "🚨 ВАЖНО: Сохраните файл master.key в надежном месте!") tk.Label(frame, text=message, font=self.body_font, bg=self.colors['primary'], fg=self.colors['text'], justify=tk.LEFT).pack(pady=(0, 30)) tk.Button(frame, text="Начать использование", font=self.heading_font, bg=self.colors['accent'], fg=self.colors['text'], command=welcome_window.destroy, padx=30, pady=10, relief='flat').pack() def show_error_screen(self, message): """Показывает экран ошибки""" error_frame = tk.Frame(self.root, bg=self.colors['primary']) error_frame.pack(expand=True, fill='both') tk.Label(error_frame, text="⚠️", font=("Segoe UI", 72), bg=self.colors['primary'], fg=self.colors['warning']).pack(pady=(100, 30)) tk.Label(error_frame, text="Ошибка", font=self.title_font, bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 20)) tk.Label(error_frame, text=message, font=self.body_font, bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 30)) tk.Button(error_frame, text="Выход", font=self.heading_font, bg=self.colors['warning'], fg=self.colors['text'], command=self.root.destroy, padx=30, pady=10, relief='flat').pack() def encrypt(self, data): """Шифрует данные""" return self.cipher.encrypt(data.encode()).decode() def decrypt(self, encrypted_data): """Расшифровывает данные""" try: return self.cipher.decrypt(encrypted_data.encode()).decode() except: return encrypted_data def load_passwords(self): """Загружает пароли из файла""" if os.path.exists(self.filename): try: with open(self.filename, 'r', encoding='utf-8') as f: encrypted_data = json.load(f) decrypted_json = self.decrypt(encrypted_data) return json.loads(decrypted_json) except: return {} return {} def save_passwords(self): """Сохраняет пароли в файл""" try: data_json = json.dumps(self.passwords, indent=2, ensure_ascii=False) encrypted_data = self.encrypt(data_json) with open(self.filename, 'w', encoding='utf-8') as f: json.dump(encrypted_data, f, ensure_ascii=False, indent=2) self.create_backup() return True except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}") return False def create_backup(self): """Создает резервную копию""" try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_file = os.path.join(self.backup_dir, f"backup_{timestamp}.enc") with open(backup_file, 'w', encoding='utf-8') as f: json.dump(self.passwords, f, ensure_ascii=False, indent=2) except: pass def export_to_txt(self): """Экспортирует все пароли в текстовый файл""" if not self.passwords: messagebox.showinfo("Информация", "Нет данных для экспорта") return try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") export_file = os.path.join(self.documents_path, f"passwords_export_{timestamp}.txt") with open(export_file, 'w', encoding='utf-8') as f: f.write("=" * 70 + "\n") f.write(" " * 20 + "ЭКСПОРТ ПАРОЛЕЙ\n") f.write("=" * 70 + "\n") f.write(f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n") f.write(f"Всего записей: {len(self.passwords)}\n") f.write("=" * 70 + "\n\n") for i, (service, data) in enumerate(sorted(self.passwords.items()), 1): f.write(f"🔐 ЗАПИСЬ #{i}\n") f.write("-" * 40 + "\n") f.write(f"Сервис: {service}\n") f.write(f"Логин: {data['username']}\n") f.write(f"Пароль: {self.decrypt(data['password'])}\n") if data['notes']: f.write(f"Заметки: {data['notes']}\n") f.write("\n" + "=" * 40 + "\n\n") f.write("\n" + "!" * 70 + "\n") f.write("ВНИМАНИЕ: Этот файл содержит пароли в открытом виде!\n") f.write("Храните его в безопасном месте и удалите после использования.\n") f.write("!" * 70 + "\n") messagebox.showinfo("Успех", f"✅ Экспорт завершен!\n\n" f"Файл сохранен: {export_file}\n\n" f"⚠️ Файл содержит пароли в открытом виде!\n" f"Рекомендуется удалить его после использования.") # Открываем файл if platform.system() == "Windows": os.startfile(export_file) elif platform.system() == "Darwin": os.system(f"open '{export_file}'") else: os.system(f"xdg-open '{export_file}'") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось экспортировать: {str(e)}") def create_main_layout(self): """Создает основной интерфейс""" # Верхняя панель self.create_header() # Основное содержание main_container = tk.Frame(self.root, bg=self.colors['secondary']) main_container.pack(fill='both', expand=True, padx=20, pady=10) # Левая панель - список left_panel = tk.Frame(main_container, bg=self.colors['primary']) left_panel.pack(side='left', fill='both', expand=True, padx=(0, 10)) # Правая панель - редактор right_panel = tk.Frame(main_container, bg=self.colors['primary']) right_panel.pack(side='right', fill='both', expand=True) # Создаем левую и правую панели self.create_left_panel(left_panel) self.create_right_panel(right_panel) # Нижняя панель self.create_footer() def create_header(self): """Создает верхнюю панель""" header = tk.Frame(self.root, bg=self.colors['primary'], height=80) header.pack(fill='x', padx=20, pady=(20, 0)) header.pack_propagate(False) # Логотип и название logo_frame = tk.Frame(header, bg=self.colors['primary']) logo_frame.pack(side='left', padx=20) tk.Label(logo_frame, text="🔐", font=("Segoe UI", 32), bg=self.colors['primary'], fg=self.colors['accent']).pack(side='left') tk.Label(logo_frame, text="SecurePass", font=self.title_font, bg=self.colors['primary'], fg=self.colors['text']).pack(side='left', padx=(10, 0)) tk.Label(logo_frame, text="Manager", font=("Segoe UI", 24, "bold"), bg=self.colors['primary'], fg=self.colors['success']).pack(side='left') # Поиск search_frame = tk.Frame(header, bg=self.colors['entry_bg']) search_frame.pack(side='right', padx=20) search_icon = tk.Label(search_frame, text="🔍", font=self.body_font, bg=self.colors['entry_bg'], fg=self.colors['text']) search_icon.pack(side='left', padx=(10, 5)) self.search_var = tk.StringVar() self.search_entry = tk.Entry(search_frame, textvariable=self.search_var, bg=self.colors['entry_bg'], fg=self.colors['text'], insertbackground=self.colors['text'], relief='flat', font=self.body_font) self.search_entry.pack(side='left', fill='x', expand=True, padx=(0, 10), pady=10, ipady=5) self.search_entry.bind('', self.on_search) # Подсказка self.search_entry.insert(0, "Поиск...") self.search_entry.config(fg='#888888') def on_focus_in(event): if self.search_entry.get() == "Поиск...": self.search_entry.delete(0, tk.END) self.search_entry.config(fg=self.colors['text']) def on_focus_out(event): if not self.search_entry.get(): self.search_entry.insert(0, "Поиск...") self.search_entry.config(fg='#888888') self.search_entry.bind('', on_focus_in) self.search_entry.bind('', on_focus_out) def create_left_panel(self, parent): """Создает левую панель со списком сервисов""" # Заголовок title_frame = tk.Frame(parent, bg=self.colors['primary']) title_frame.pack(fill='x', padx=20, pady=(20, 10)) tk.Label(title_frame, text="Ваши сервисы", font=self.heading_font, bg=self.colors['primary'], fg=self.colors['text']).pack(side='left') count_frame = tk.Frame(title_frame, bg=self.colors['accent'], relief='flat', bd=0) count_frame.pack(side='right', padx=(10, 0)) self.count_label = tk.Label(count_frame, text="0", font=("Segoe UI", 10, "bold"), bg=self.colors['accent'], fg=self.colors['text'], padx=8, pady=2) self.count_label.pack() # Список с прокруткой list_frame = tk.Frame(parent, bg=self.colors['primary']) list_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20)) # Создаем Canvas и Scrollbar canvas = tk.Canvas(list_frame, bg=self.colors['primary'], highlightthickness=0) scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=canvas.yview) self.scrollable_frame = tk.Frame(canvas, bg=self.colors['primary']) self.scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # Упаковываем canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Кнопки действий button_frame = tk.Frame(parent, bg=self.colors['primary']) button_frame.pack(fill='x', padx=20, pady=(0, 20)) actions = [ ("➕ Добавить", self.add_password, self.colors['success']), ("✏️ Редактировать", self.update_password, self.colors['accent']), ("🗑️ Удалить", self.delete_password, self.colors['warning']) ] for text, command, color in actions: btn = tk.Button(button_frame, text=text, font=self.body_font, bg=color, fg=self.colors['text'], command=command, relief='flat', padx=20, pady=10) btn.pack(side='left', expand=True, fill='x', padx=(0, 10)) btn.bind("", lambda e, b=btn: b.config(bg=self.colors['hover'])) btn.bind("", lambda e, b=btn, c=color: b.config(bg=c)) def create_right_panel(self, parent): """Создает правую панель с редактором""" # Заголовок title_frame = tk.Frame(parent, bg=self.colors['primary']) title_frame.pack(fill='x', padx=20, pady=(20, 10)) self.editor_title = tk.Label(title_frame, text="Новая запись", font=self.heading_font, bg=self.colors['primary'], fg=self.colors['text']) self.editor_title.pack(side='left') # Карточка формы form_card = tk.Frame(parent, bg=self.colors['card_bg'], relief='flat', bd=0) form_card.pack(fill='both', expand=True, padx=20, pady=(0, 20)) # Поля формы fields = [ ("🌐 Сервис/Сайт", "service"), ("👤 Логин/Email", "username"), ("🔑 Пароль", "password"), ("📝 Заметки", "notes") ] self.entries = {} for i, (label_text, field_name) in enumerate(fields): frame = tk.Frame(form_card, bg=self.colors['card_bg']) frame.pack(fill='x', padx=30, pady=15) # Метка tk.Label(frame, text=label_text, font=self.body_font, bg=self.colors['card_bg'], fg=self.colors['text'], width=15, anchor='w').pack(side='left') if field_name == "notes": # Многострочное поле для заметок entry_frame = tk.Frame(frame, bg=self.colors['entry_bg']) entry_frame.pack(side='left', fill='both', expand=True, padx=(10, 0)) entry = scrolledtext.ScrolledText(entry_frame, bg=self.colors['entry_bg'], fg=self.colors['text'], insertbackground=self.colors['text'], font=self.body_font, relief='flat', height=4) entry.pack(fill='both', expand=True, padx=5, pady=5) elif field_name == "password": # Поле пароля с кнопками entry_frame = tk.Frame(frame, bg=self.colors['entry_bg']) entry_frame.pack(side='left', fill='x', expand=True, padx=(10, 0)) self.password_var = tk.StringVar() entry = tk.Entry(entry_frame, textvariable=self.password_var, bg=self.colors['entry_bg'], fg=self.colors['text'], insertbackground=self.colors['text'], font=self.mono_font, relief='flat', show="●") entry.pack(side='left', fill='x', expand=True, padx=(5, 0), pady=5, ipady=5) # Кнопки управления паролем btn_frame = tk.Frame(entry_frame, bg=self.colors['entry_bg']) btn_frame.pack(side='right', padx=(5, 5)) buttons = [ ("👁", self.toggle_password_visibility), ("📋", self.copy_password), ("🎲", self.generate_password) ] for btn_text, btn_command in buttons: btn = tk.Button(btn_frame, text=btn_text, font=self.body_font, bg=self.colors['secondary'], fg=self.colors['text'], command=btn_command, relief='flat', width=3) btn.pack(side='left', padx=2) btn.bind("", lambda e, b=btn: b.config(bg=self.colors['hover'])) btn.bind("", lambda e, b=btn: b.config(bg=self.colors['secondary'])) else: # Обычное поле ввода entry_frame = tk.Frame(frame, bg=self.colors['entry_bg']) entry_frame.pack(side='left', fill='x', expand=True, padx=(10, 0)) entry = tk.Entry(entry_frame, bg=self.colors['entry_bg'], fg=self.colors['text'], insertbackground=self.colors['text'], font=self.body_font, relief='flat') entry.pack(fill='x', expand=True, padx=5, pady=5, ipady=5) self.entries[field_name] = entry # Кнопки сохранения button_frame = tk.Frame(form_card, bg=self.colors['card_bg']) button_frame.pack(fill='x', padx=30, pady=(20, 30)) save_btn = tk.Button(button_frame, text="💾 Сохранить", font=self.heading_font, bg=self.colors['success'], fg=self.colors['text'], command=self.save_current, relief='flat', padx=40, pady=12) save_btn.pack(side='left') save_btn.bind("", lambda e: save_btn.config(bg=self.colors['hover'])) save_btn.bind("", lambda e: save_btn.config(bg=self.colors['success'])) clear_btn = tk.Button(button_frame, text="🔄 Очистить", font=self.heading_font, bg=self.colors['secondary'], fg=self.colors['text'], command=self.clear_form, relief='flat', padx=40, pady=12) clear_btn.pack(side='left', padx=(20, 0)) clear_btn.bind("", lambda e: clear_btn.config(bg=self.colors['hover'])) clear_btn.bind("", lambda e: clear_btn.config(bg=self.colors['secondary'])) def create_footer(self): """Создает нижнюю панель""" footer = tk.Frame(self.root, bg=self.colors['primary'], height=60) footer.pack(fill='x', side='bottom', padx=20, pady=(0, 20)) footer.pack_propagate(False) # Левая часть - информация left_info = tk.Frame(footer, bg=self.colors['primary']) left_info.pack(side='left', padx=20) stats_text = f"🛡️ Защищено записей: {len(self.passwords)}" self.stats_label = tk.Label(left_info, text=stats_text, font=self.body_font, bg=self.colors['primary'], fg=self.colors['text']) self.stats_label.pack(side='left') # Правая часть - кнопки right_buttons = tk.Frame(footer, bg=self.colors['primary']) right_buttons.pack(side='right', padx=20) export_btn = tk.Button(right_buttons, text="📄 Экспорт в TXT", font=self.body_font, bg=self.colors['accent'], fg=self.colors['text'], command=self.export_to_txt, relief='flat', padx=20, pady=8) export_btn.pack(side='left', padx=(0, 10)) export_btn.bind("", lambda e: export_btn.config(bg=self.colors['hover'])) export_btn.bind("", lambda e: export_btn.config(bg=self.colors['accent'])) backup_btn = tk.Button(right_buttons, text="💾 Создать резервную копию", font=self.body_font, bg=self.colors['secondary'], fg=self.colors['text'], command=self.create_manual_backup, relief='flat', padx=20, pady=8) backup_btn.pack(side='left') backup_btn.bind("", lambda e: backup_btn.config(bg=self.colors['hover'])) backup_btn.bind("", lambda e: backup_btn.config(bg=self.colors['secondary'])) def create_manual_backup(self): """Создает ручную резервную копию""" try: self.save_passwords() messagebox.showinfo("Успех", "✅ Резервная копия создана!") except: messagebox.showerror("Ошибка", "Не удалось создать резервную копию") def update_service_list(self, search_term=None): """Обновляет список сервисов""" # Очищаем текущий список for widget in self.scrollable_frame.winfo_children(): widget.destroy() services = list(self.passwords.keys()) if search_term and search_term != "Поиск...": search_term = search_term.lower() services = [s for s in services if search_term in s.lower()] self.count_label.config(text=str(len(services))) self.stats_label.config(text=f"🛡️ Защищено записей: {len(self.passwords)}") if not services: empty_label = tk.Label(self.scrollable_frame, text="Нет сохраненных сервисов", font=self.body_font, bg=self.colors['primary'], fg='#888888', pady=20) empty_label.pack() return for service in sorted(services): self.create_service_card(service) def create_service_card(self, service): """Создает карточку сервиса""" card = tk.Frame(self.scrollable_frame, bg=self.colors['card_bg'], relief='flat', bd=0) card.pack(fill='x', pady=5, padx=5) # Основное содержимое карточки content_frame = tk.Frame(card, bg=self.colors['card_bg']) content_frame.pack(fill='x', padx=15, pady=10) # Иконка (первая буква сервиса) icon_frame = tk.Frame(content_frame, bg=self.colors['accent'], width=40, height=40) icon_frame.pack(side='left') icon_frame.pack_propagate(False) icon_label = tk.Label(icon_frame, text=service[0].upper(), font=("Segoe UI", 16, "bold"), bg=self.colors['accent'], fg=self.colors['text']) icon_label.pack(expand=True) # Информация info_frame = tk.Frame(content_frame, bg=self.colors['card_bg']) info_frame.pack(side='left', fill='x', expand=True, padx=(15, 0)) # Название сервиса name_label = tk.Label(info_frame, text=service, font=self.heading_font, bg=self.colors['card_bg'], fg=self.colors['text'], anchor='w') name_label.pack(fill='x') # Логин if service in self.passwords: username = self.passwords[service]['username'] user_label = tk.Label(info_frame, text=f"👤 {username}", font=self.body_font, bg=self.colors['card_bg'], fg='#AAAAAA', anchor='w') user_label.pack(fill='x', pady=(2, 0)) # Привязываем клик card.bind("", lambda e, s=service: self.select_service(s)) content_frame.bind("", lambda e, s=service: self.select_service(s)) icon_frame.bind("", lambda e, s=service: self.select_service(s)) info_frame.bind("", lambda e, s=service: self.select_service(s)) name_label.bind("", lambda e, s=service: self.select_service(s)) if 'user_label' in locals(): user_label.bind("", lambda e, s=service: self.select_service(s)) # Эффект при наведении def on_enter(e): card.config(bg=self.colors['hover']) content_frame.config(bg=self.colors['hover']) info_frame.config(bg=self.colors['hover']) name_label.config(bg=self.colors['hover']) if 'user_label' in locals(): user_label.config(bg=self.colors['hover']) def on_leave(e): card.config(bg=self.colors['card_bg']) content_frame.config(bg=self.colors['card_bg']) info_frame.config(bg=self.colors['card_bg']) name_label.config(bg=self.colors['card_bg']) if 'user_label' in locals(): user_label.config(bg=self.colors['card_bg']) card.bind("", on_enter) card.bind("", on_leave) content_frame.bind("", on_enter) content_frame.bind("", on_leave) def select_service(self, service): """Выбирает сервис из списка""" if service in self.passwords: data = self.passwords[service] # Обновляем заголовок self.editor_title.config(text=f"Редактирование: {service}") # Заполняем поля for widget in self.scrollable_frame.winfo_children(): if hasattr(widget, 'selected'): widget.config(bg=self.colors['card_bg']) self.entries['service'].delete(0, tk.END) self.entries['service'].insert(0, service) self.entries['username'].delete(0, tk.END) self.entries['username'].insert(0, data['username']) password = self.decrypt(data['password']) self.password_var.set(password) self.entries['notes'].delete('1.0', tk.END) self.entries['notes'].insert('1.0', data['notes']) def on_search(self, event=None): """Обрабатывает поиск""" search_term = self.search_var.get() if search_term == "Поиск...": search_term = "" self.update_service_list(search_term) def add_password(self): """Добавляет новую запись""" self.clear_form() self.editor_title.config(text="Новая запись") self.entries['service'].focus() def clear_form(self): """Очищает форму""" self.editor_title.config(text="Новая запись") self.entries['service'].delete(0, tk.END) self.entries['username'].delete(0, tk.END) self.password_var.set("") self.entries['notes'].delete('1.0', tk.END) # Сбрасываем выделение в списке for widget in self.scrollable_frame.winfo_children(): if hasattr(widget, 'selected'): widget.config(bg=self.colors['card_bg']) def save_current(self): """Сохраняет текущую запись""" service = self.entries['service'].get().strip() username = self.entries['username'].get().strip() password = self.password_var.get().strip() if not service: messagebox.showerror("Ошибка", "Введите название сервиса") self.entries['service'].focus() return if not username: messagebox.showerror("Ошибка", "Введите логин или email") self.entries['username'].focus() return if not password: messagebox.showerror("Ошибка", "Введите пароль") self.entries['password'].focus() return notes = self.entries['notes'].get('1.0', tk.END).strip() # Шифруем пароль encrypted_password = self.encrypt(password) self.passwords[service] = { 'username': username, 'password': encrypted_password, 'notes': notes } if self.save_passwords(): self.update_service_list(self.search_var.get() if self.search_var.get() != "Поиск..." else None) # Показываем анимацию успеха self.show_success_animation("Запись сохранена!") def update_password(self): """Обновляет выбранную запись""" service = self.entries['service'].get().strip() if not service or service not in self.passwords: messagebox.showwarning("Внимание", "Выберите запись для редактирования") return self.save_current() def delete_password(self): """Удаляет выбранную запись""" service = self.entries['service'].get().strip() if not service or service not in self.passwords: messagebox.showwarning("Внимание", "Выберите запись для удаления") return if messagebox.askyesno("Подтверждение", f"Вы уверены, что хотите удалить запись '{service}'?", icon='warning'): del self.passwords[service] self.save_passwords() self.update_service_list(self.search_var.get() if self.search_var.get() != "Поиск..." else None) self.clear_form() self.show_success_animation("Запись удалена!") def toggle_password_visibility(self): """Показывает/скрывает пароль""" current_state = self.entries['password'].cget('show') if current_state == "●": self.entries['password'].config(show="") else: self.entries['password'].config(show="●") def copy_password(self): """Копирует пароль в буфер обмена""" password = self.password_var.get() if password: pyperclip.copy(password) self.show_temp_message("Пароль скопирован!") def generate_password(self): """Генерирует случайный пароль""" length = 16 chars = string.ascii_letters + string.digits + "!@#$%^&*" password = ''.join(random.choice(chars) for _ in range(length)) self.password_var.set(password) self.show_temp_message("Пароль сгенерирован!") def show_success_animation(self, message): """Показывает анимацию успеха""" # Можно добавить более сложную анимацию messagebox.showinfo("Успех", f"✅ {message}") def show_temp_message(self, message): """Показывает временное сообщение""" # Можно реализовать тост-уведомление print(f"📢 {message}") def animate_welcome(self): """Простая анимация приветствия""" # Можно добавить анимацию появления элементов pass def main(): root = tk.Tk() # Проверяем библиотеки try: import cryptography import pyperclip except ImportError: # Простое окно с ошибкой error_root = tk.Tk() error_root.title("Ошибка") error_root.geometry("400x200") tk.Label(error_root, text="❌ Необходимо установить библиотеки:", font=("Segoe UI", 14)).pack(pady=20) tk.Label(error_root, text="pip install cryptography pyperclip", font=("Consolas", 12)).pack(pady=10) tk.Button(error_root, text="Выход", command=error_root.destroy, padx=20, pady=10).pack(pady=20) error_root.mainloop() return # Запускаем приложение app = ModernPasswordManager(root) root.mainloop() if __name__ == "__main__": main()