Przeglądaj źródła

Merge branch 'master' of u23-27moroz/up into master

ypv 1 tydzień temu
rodzic
commit
a74a88b135
2 zmienionych plików z 1102 dodań i 16 usunięć
  1. 886 0
      PasswordManager_by SashkaMoroz.py
  2. 216 16
      README.md

+ 886 - 0
PasswordManager_by SashkaMoroz.py

@@ -0,0 +1,886 @@
+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('<KeyRelease>', 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('<FocusIn>', on_focus_in)
+        self.search_entry.bind('<FocusOut>', 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(
+            "<Configure>",
+            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("<Enter>", lambda e, b=btn: b.config(bg=self.colors['hover']))
+            btn.bind("<Leave>", 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("<Enter>", lambda e, b=btn: b.config(bg=self.colors['hover']))
+                    btn.bind("<Leave>", 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("<Enter>", lambda e: save_btn.config(bg=self.colors['hover']))
+        save_btn.bind("<Leave>", 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("<Enter>", lambda e: clear_btn.config(bg=self.colors['hover']))
+        clear_btn.bind("<Leave>", 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("<Enter>", lambda e: export_btn.config(bg=self.colors['hover']))
+        export_btn.bind("<Leave>", 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("<Enter>", lambda e: backup_btn.config(bg=self.colors['hover']))
+        backup_btn.bind("<Leave>", 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("<Button-1>", lambda e, s=service: self.select_service(s))
+        content_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
+        icon_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
+        info_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
+        name_label.bind("<Button-1>", lambda e, s=service: self.select_service(s))
+        if 'user_label' in locals():
+            user_label.bind("<Button-1>", 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("<Enter>", on_enter)
+        card.bind("<Leave>", on_leave)
+        content_frame.bind("<Enter>", on_enter)
+        content_frame.bind("<Leave>", 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()

+ 216 - 16
README.md

@@ -1,26 +1,226 @@
-## [Опросник](https://forms.yandex.ru/u/6919eeaa90fa7b55e6668ee4/)
+🔐 SecurePass Manager
 
+Современный менеджер паролей с графическим интерфейсом и продвинутым шифрованием
 
-## [Дело в чате: эволюция нейросетей и цифровая безопасность](https://урокцифры.рф/lessons/ai-evolution)
+✨ Особенности
+🔐 Безопасность
+Военное шифрование - использует алгоритм Fernet (AES-128)
 
+Локальное хранение - все данные на вашем компьютере
 
-Первая строка должна начинаться с # и в точности совпадать с наименованием лекции из файла со списком лекций (кроме цифр)
+Защищенный мастер-ключ - отдельный файл с ключом шифрования
 
-## Вопросы
-1. Вопрос №1  
-    - Ответ №1  
-    - **Ответ №2** - правильный отмечается полужирным шрифтом  
-    - Ответ №3  
-    - Ответ №4  
+Автоматические бэкапы - резервные копии создаются автоматически
 
-2. Вопрос №2  
-    - Ответ №1  
-    - **Ответ №2**  
-    - Ответ №3  
-    - **Ответ №4**  
-и т.д.  
+🎨 Интерфейс
+Темная тема - современный дизайн для комфортной работы
 
-## Список литературы
+Карточный дизайн - интуитивно понятное представление данных
 
+Анимации - плавные переходы и эффекты
 
+Адаптивный layout - подстраивается под размер окна
 
+⚡ Функциональность
+✅ Хранение паролей - логины, пароли, заметки
+
+✅ Шифрование данных - все пароли зашифрованы
+
+✅ Поиск - мгновенный поиск по всем записям
+
+✅ Генератор паролей - создание сложных случайных паролей
+
+✅ Экспорт в TXT - сохранение всех паролей в текстовый файл
+
+✅ Резервное копирование - автоматические и ручные бэкапы
+
+✅ Копирование в буфер - быстрый доступ к паролям
+
+📦 Установка
+Требования
+Python 3.8 или выше
+
+pip (менеджер пакетов Python)
+
+Установите зависимости:
+
+pip install cryptography pyperclip
+Запустите приложение:
+
+
+python securepass_manager.py
+
+Распакуйте архив
+
+Запустите SecurePassManager.exe
+
+🚀 Использование
+Первый запуск
+При первом запуске создается мастер-ключ (master.key)
+
+Сохраните этот файл в безопасном месте!
+
+Без него невозможно восстановить ваши пароли
+
+Создайте свою первую запись пароля
+
+Основные функции
+📝 Добавление записи
+Нажмите кнопку "➕ Добавить"
+
+Заполните поля:
+
+🌐 Сервис/Сайт
+
+👤 Логин/Email
+
+🔑 Пароль
+
+📝 Заметки (опционально)
+
+Нажмите "💾 Сохранить"
+
+🔍 Поиск записей
+Используйте поле поиска в верхней панели
+
+Поиск работает по названиям сервисов
+
+Результаты обновляются в реальном времени
+
+🎲 Генератор паролей
+Нажмите кнопку "🎲" в поле пароля
+
+Будет сгенерирован сложный 16-символьный пароль
+
+Пароль автоматически копируется в поле
+
+📄 Экспорт паролей
+Нажмите "📄 Экспорт в TXT" в нижней панели
+
+Все пароли будут сохранены в текстовый файл
+
+Файл откроется автоматически
+
+⚠️ Файл содержит пароли в открытом виде!
+
+💾 Резервное копирование
+Автоматически: При каждом сохранении
+
+Вручную: Кнопка "💾 Создать резервную копию"
+
+Хранятся последние 10 версий
+
+📁 Структура файлов
+text
+📁 Документы/
+ └── 📁 SecurePassManager/
+     ├── 🔐 passwords.enc      # Зашифрованные пароли
+     ├── 🔑 master.key         # Ключ шифрования (ВАЖНО!)
+     └── 📁 backups/
+         ├── 📄 backup_20241215_143022.enc
+         └── 📄 backup_20241215_150045.enc
+🔧 Технические детали
+Технологии
+Python 3.8+ - основной язык программирования
+
+Tkinter - графический интерфейс
+
+Cryptography - библиотека шифрования
+
+Pyperclip - работа с буфером обмена
+
+Шифрование
+Алгоритм: Fernet (AES-128 в режиме CBC)
+
+Ключ: 256-битный случайный ключ
+
+Соль: Уникальная для каждого шифрования
+
+HMAC: Проверка целостности данных
+
+Безопасность данных
+Все пароли шифруются перед сохранением
+
+Ключ шифрования хранится отдельно от данных
+
+Данные никогда не покидают ваш компьютер
+
+Нет подключения к интернету
+
+⚠️ Важные предупреждения
+🚨 Безопасность ключа
+text
+ВНИМАНИЕ: Файл master.key критически важен!
+• Без него ВОЗМОЖНОСТЬ ВОССТАНОВЛЕНИЯ ПАРОЛЕЙ = 0%
+• Храните его на отдельной флешке или в сейфе
+• Сделайте несколько копий в разных местах
+• Никогда не передавайте его другим людям
+📄 Экспорт в TXT
+Файл экспорта содержит пароли в открытом виде
+
+Удаляйте файл после использования
+
+Не отправляйте его по email или мессенджерам
+
+Храните на зашифрованном диске
+
+🔄 Миграция данных
+Импорт из других менеджеров
+Экспортируйте пароли из старого менеджера в CSV/TXT
+
+Откройте файл экспорта
+
+Вручную добавьте записи в SecurePass Manager
+
+Перенос на другой компьютер
+Скопируйте папку SecurePassManager из Документов
+
+Перенесите ее на новый компьютер
+
+Установите SecurePass Manager
+
+Запустите приложение
+
+📱 Поддерживаемые платформы
+Платформа	Поддержка	Примечания
+Windows 10/11	✅ Полная	Рекомендуемая ОС
+macOS 10.15+	✅ Полная	Требуется Python 3.8+
+Linux (Ubuntu/Debian)	✅ Полная	Требуется установка Python
+Android/iOS	❌ Нет поддержки	Только десктоп
+🐛 Отладка и проблемы
+Распространенные проблемы
+Проблема: "Не удалось загрузить ключ шифрования"
+Решение:
+
+Проверьте наличие файла master.key
+
+Убедитесь в правах доступа к файлу
+
+Если файл поврежден - используйте резервную копию
+
+Проблема: "Не удалось сохранить данные"
+Решение:
+
+Проверьте доступность папки Документы
+
+Убедитесь в достаточном месте на диске
+
+Проверьте права на запись
+
+Проблема: "Пароли не отображаются"
+Решение:
+
+Проверьте файл passwords.enc
+
+Убедитесь в корректности master.key
+
+Попробуйте восстановить из бэкапа
+
+Логирование
+При возникновении ошибок проверьте:
+
+Консоль Python (если запущено из терминала)
+
+Файлы в папке SecurePassManager/backups/
+
+Файл журнала (если включено логирование)