| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 |
- 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()
|