PasswordManager_by SashkaMoroz.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. import tkinter as tk
  2. from tkinter import ttk, messagebox, scrolledtext, filedialog
  3. import json
  4. import base64
  5. from cryptography.fernet import Fernet
  6. import os
  7. import pyperclip
  8. from datetime import datetime
  9. import getpass
  10. import platform
  11. import random
  12. import string
  13. from tkinter import font as tkfont
  14. import time
  15. # Установите библиотеки если их нет:
  16. # pip install cryptography pyperclip
  17. class ModernPasswordManager:
  18. def __init__(self, root):
  19. self.root = root
  20. self.root.title("🔐 SecurePass Manager")
  21. self.root.geometry("1200x750")
  22. # Устанавливаем иконку (если есть)
  23. try:
  24. self.root.iconbitmap('icon.ico') # Можно создать иконку
  25. except:
  26. pass
  27. # Центрирование окна
  28. self.center_window()
  29. # Современная цветовая схема
  30. self.colors = {
  31. 'primary': '#2A2D43', # Темно-синий
  32. 'secondary': '#1E1F29', # Более темный фон
  33. 'accent': '#4A6FA5', # Голубой акцент
  34. 'success': '#50C878', # Изумрудный
  35. 'warning': '#FF6B6B', # Красный/коралловый
  36. 'text': '#E8EAED', # Светлый текст
  37. 'entry_bg': '#3C3F58', # Фон полей ввода
  38. 'card_bg': '#34374C', # Фон карточек
  39. 'hover': '#5A6FA5', # Цвет при наведении
  40. 'gradient_start': '#667eea', # Для градиентов
  41. 'gradient_end': '#764ba2'
  42. }
  43. # Загружаем шрифты
  44. self.load_fonts()
  45. # Настройка стилей
  46. self.setup_styles()
  47. # Получаем путь к Документам
  48. self.documents_path = self.get_documents_path()
  49. # Инициализация файлов
  50. self.data_dir = os.path.join(self.documents_path, "SecurePassManager")
  51. os.makedirs(self.data_dir, exist_ok=True)
  52. self.filename = os.path.join(self.data_dir, "passwords.enc")
  53. self.keyfile = os.path.join(self.data_dir, "master.key")
  54. self.backup_dir = os.path.join(self.data_dir, "backups")
  55. os.makedirs(self.backup_dir, exist_ok=True)
  56. # Загрузка или создание ключа
  57. self.key = self.load_or_create_key()
  58. if self.key:
  59. self.cipher = Fernet(self.key)
  60. self.passwords = self.load_passwords()
  61. else:
  62. self.show_error_screen("Не удалось инициализировать систему шифрования")
  63. return
  64. # Создание интерфейса
  65. self.create_main_layout()
  66. self.update_service_list()
  67. # Запуск анимаций
  68. self.root.after(100, self.animate_welcome)
  69. def center_window(self):
  70. """Центрирует окно на экране"""
  71. self.root.update_idletasks()
  72. width = self.root.winfo_width()
  73. height = self.root.winfo_height()
  74. x = (self.root.winfo_screenwidth() // 2) - (width // 2)
  75. y = (self.root.winfo_screenheight() // 2) - (height // 2)
  76. self.root.geometry(f'{width}x{height}+{x}+{y}')
  77. def load_fonts(self):
  78. """Загружаем шрифты"""
  79. self.title_font = ("Segoe UI", 24, "bold")
  80. self.heading_font = ("Segoe UI", 14, "bold")
  81. self.body_font = ("Segoe UI", 10)
  82. self.mono_font = ("Consolas", 10)
  83. def setup_styles(self):
  84. """Настройка стилей для виджетов"""
  85. style = ttk.Style()
  86. style.theme_use('clam')
  87. # Настраиваем стили
  88. style.configure('Modern.TButton',
  89. font=self.body_font,
  90. borderwidth=0,
  91. relief='flat',
  92. padding=10)
  93. style.configure('Card.TFrame',
  94. background=self.colors['card_bg'],
  95. relief='flat',
  96. borderwidth=0)
  97. style.configure('Primary.TButton',
  98. font=self.body_font,
  99. background=self.colors['accent'],
  100. foreground=self.colors['text'],
  101. borderwidth=0)
  102. def get_documents_path(self):
  103. """Получает путь к папке Документы"""
  104. system = platform.system()
  105. if system == "Windows":
  106. import ctypes.wintypes
  107. CSIDL_PERSONAL = 5
  108. SHGFP_TYPE_CURRENT = 0
  109. buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
  110. ctypes.windll.shell32.SHGetFolderPathW(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, buf)
  111. return buf.value
  112. elif system == "Darwin":
  113. return os.path.expanduser("~/Documents")
  114. else:
  115. return os.path.expanduser("~/Documents")
  116. def load_or_create_key(self):
  117. """Загружает или создает ключ шифрования"""
  118. if os.path.exists(self.keyfile):
  119. try:
  120. with open(self.keyfile, 'rb') as f:
  121. return f.read()
  122. except:
  123. return None
  124. else:
  125. try:
  126. key = Fernet.generate_key()
  127. with open(self.keyfile, 'wb') as f:
  128. f.write(key)
  129. self.show_welcome_message()
  130. return key
  131. except:
  132. return None
  133. def show_welcome_message(self):
  134. """Показывает приветственное сообщение"""
  135. welcome_window = tk.Toplevel(self.root)
  136. welcome_window.title("Добро пожаловать!")
  137. welcome_window.geometry("500x400")
  138. welcome_window.configure(bg=self.colors['primary'])
  139. welcome_window.resizable(False, False)
  140. # Центрируем
  141. welcome_window.update_idletasks()
  142. width = welcome_window.winfo_width()
  143. height = welcome_window.winfo_height()
  144. x = (welcome_window.winfo_screenwidth() // 2) - (width // 2)
  145. y = (welcome_window.winfo_screenheight() // 2) - (height // 2)
  146. welcome_window.geometry(f'{width}x{height}+{x}+{y}')
  147. # Содержимое
  148. frame = tk.Frame(welcome_window, bg=self.colors['primary'])
  149. frame.pack(expand=True, fill='both', padx=40, pady=40)
  150. tk.Label(frame, text="🔐", font=("Segoe UI", 48),
  151. bg=self.colors['primary'], fg=self.colors['accent']).pack(pady=(0, 20))
  152. tk.Label(frame, text="SecurePass Manager", font=self.title_font,
  153. bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 10))
  154. message = ("Добро пожаловать в SecurePass Manager!\n\n"
  155. "• Все ваши пароли защищены современным шифрованием\n"
  156. "• Ключ шифрования сохранен в безопасном месте\n"
  157. "• Данные хранятся локально на вашем компьютере\n\n"
  158. "🚨 ВАЖНО: Сохраните файл master.key в надежном месте!")
  159. tk.Label(frame, text=message, font=self.body_font,
  160. bg=self.colors['primary'], fg=self.colors['text'],
  161. justify=tk.LEFT).pack(pady=(0, 30))
  162. tk.Button(frame, text="Начать использование",
  163. font=self.heading_font,
  164. bg=self.colors['accent'], fg=self.colors['text'],
  165. command=welcome_window.destroy,
  166. padx=30, pady=10, relief='flat').pack()
  167. def show_error_screen(self, message):
  168. """Показывает экран ошибки"""
  169. error_frame = tk.Frame(self.root, bg=self.colors['primary'])
  170. error_frame.pack(expand=True, fill='both')
  171. tk.Label(error_frame, text="⚠️", font=("Segoe UI", 72),
  172. bg=self.colors['primary'], fg=self.colors['warning']).pack(pady=(100, 30))
  173. tk.Label(error_frame, text="Ошибка", font=self.title_font,
  174. bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 20))
  175. tk.Label(error_frame, text=message, font=self.body_font,
  176. bg=self.colors['primary'], fg=self.colors['text']).pack(pady=(0, 30))
  177. tk.Button(error_frame, text="Выход", font=self.heading_font,
  178. bg=self.colors['warning'], fg=self.colors['text'],
  179. command=self.root.destroy,
  180. padx=30, pady=10, relief='flat').pack()
  181. def encrypt(self, data):
  182. """Шифрует данные"""
  183. return self.cipher.encrypt(data.encode()).decode()
  184. def decrypt(self, encrypted_data):
  185. """Расшифровывает данные"""
  186. try:
  187. return self.cipher.decrypt(encrypted_data.encode()).decode()
  188. except:
  189. return encrypted_data
  190. def load_passwords(self):
  191. """Загружает пароли из файла"""
  192. if os.path.exists(self.filename):
  193. try:
  194. with open(self.filename, 'r', encoding='utf-8') as f:
  195. encrypted_data = json.load(f)
  196. decrypted_json = self.decrypt(encrypted_data)
  197. return json.loads(decrypted_json)
  198. except:
  199. return {}
  200. return {}
  201. def save_passwords(self):
  202. """Сохраняет пароли в файл"""
  203. try:
  204. data_json = json.dumps(self.passwords, indent=2, ensure_ascii=False)
  205. encrypted_data = self.encrypt(data_json)
  206. with open(self.filename, 'w', encoding='utf-8') as f:
  207. json.dump(encrypted_data, f, ensure_ascii=False, indent=2)
  208. self.create_backup()
  209. return True
  210. except Exception as e:
  211. messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}")
  212. return False
  213. def create_backup(self):
  214. """Создает резервную копию"""
  215. try:
  216. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  217. backup_file = os.path.join(self.backup_dir, f"backup_{timestamp}.enc")
  218. with open(backup_file, 'w', encoding='utf-8') as f:
  219. json.dump(self.passwords, f, ensure_ascii=False, indent=2)
  220. except:
  221. pass
  222. def export_to_txt(self):
  223. """Экспортирует все пароли в текстовый файл"""
  224. if not self.passwords:
  225. messagebox.showinfo("Информация", "Нет данных для экспорта")
  226. return
  227. try:
  228. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  229. export_file = os.path.join(self.documents_path, f"passwords_export_{timestamp}.txt")
  230. with open(export_file, 'w', encoding='utf-8') as f:
  231. f.write("=" * 70 + "\n")
  232. f.write(" " * 20 + "ЭКСПОРТ ПАРОЛЕЙ\n")
  233. f.write("=" * 70 + "\n")
  234. f.write(f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n")
  235. f.write(f"Всего записей: {len(self.passwords)}\n")
  236. f.write("=" * 70 + "\n\n")
  237. for i, (service, data) in enumerate(sorted(self.passwords.items()), 1):
  238. f.write(f"🔐 ЗАПИСЬ #{i}\n")
  239. f.write("-" * 40 + "\n")
  240. f.write(f"Сервис: {service}\n")
  241. f.write(f"Логин: {data['username']}\n")
  242. f.write(f"Пароль: {self.decrypt(data['password'])}\n")
  243. if data['notes']:
  244. f.write(f"Заметки: {data['notes']}\n")
  245. f.write("\n" + "=" * 40 + "\n\n")
  246. f.write("\n" + "!" * 70 + "\n")
  247. f.write("ВНИМАНИЕ: Этот файл содержит пароли в открытом виде!\n")
  248. f.write("Храните его в безопасном месте и удалите после использования.\n")
  249. f.write("!" * 70 + "\n")
  250. messagebox.showinfo("Успех",
  251. f"✅ Экспорт завершен!\n\n"
  252. f"Файл сохранен: {export_file}\n\n"
  253. f"⚠️ Файл содержит пароли в открытом виде!\n"
  254. f"Рекомендуется удалить его после использования.")
  255. # Открываем файл
  256. if platform.system() == "Windows":
  257. os.startfile(export_file)
  258. elif platform.system() == "Darwin":
  259. os.system(f"open '{export_file}'")
  260. else:
  261. os.system(f"xdg-open '{export_file}'")
  262. except Exception as e:
  263. messagebox.showerror("Ошибка", f"Не удалось экспортировать: {str(e)}")
  264. def create_main_layout(self):
  265. """Создает основной интерфейс"""
  266. # Верхняя панель
  267. self.create_header()
  268. # Основное содержание
  269. main_container = tk.Frame(self.root, bg=self.colors['secondary'])
  270. main_container.pack(fill='both', expand=True, padx=20, pady=10)
  271. # Левая панель - список
  272. left_panel = tk.Frame(main_container, bg=self.colors['primary'])
  273. left_panel.pack(side='left', fill='both', expand=True, padx=(0, 10))
  274. # Правая панель - редактор
  275. right_panel = tk.Frame(main_container, bg=self.colors['primary'])
  276. right_panel.pack(side='right', fill='both', expand=True)
  277. # Создаем левую и правую панели
  278. self.create_left_panel(left_panel)
  279. self.create_right_panel(right_panel)
  280. # Нижняя панель
  281. self.create_footer()
  282. def create_header(self):
  283. """Создает верхнюю панель"""
  284. header = tk.Frame(self.root, bg=self.colors['primary'], height=80)
  285. header.pack(fill='x', padx=20, pady=(20, 0))
  286. header.pack_propagate(False)
  287. # Логотип и название
  288. logo_frame = tk.Frame(header, bg=self.colors['primary'])
  289. logo_frame.pack(side='left', padx=20)
  290. tk.Label(logo_frame, text="🔐", font=("Segoe UI", 32),
  291. bg=self.colors['primary'], fg=self.colors['accent']).pack(side='left')
  292. tk.Label(logo_frame, text="SecurePass", font=self.title_font,
  293. bg=self.colors['primary'], fg=self.colors['text']).pack(side='left', padx=(10, 0))
  294. tk.Label(logo_frame, text="Manager", font=("Segoe UI", 24, "bold"),
  295. bg=self.colors['primary'], fg=self.colors['success']).pack(side='left')
  296. # Поиск
  297. search_frame = tk.Frame(header, bg=self.colors['entry_bg'])
  298. search_frame.pack(side='right', padx=20)
  299. search_icon = tk.Label(search_frame, text="🔍", font=self.body_font,
  300. bg=self.colors['entry_bg'], fg=self.colors['text'])
  301. search_icon.pack(side='left', padx=(10, 5))
  302. self.search_var = tk.StringVar()
  303. self.search_entry = tk.Entry(search_frame, textvariable=self.search_var,
  304. bg=self.colors['entry_bg'], fg=self.colors['text'],
  305. insertbackground=self.colors['text'],
  306. relief='flat', font=self.body_font)
  307. self.search_entry.pack(side='left', fill='x', expand=True, padx=(0, 10), pady=10, ipady=5)
  308. self.search_entry.bind('<KeyRelease>', self.on_search)
  309. # Подсказка
  310. self.search_entry.insert(0, "Поиск...")
  311. self.search_entry.config(fg='#888888')
  312. def on_focus_in(event):
  313. if self.search_entry.get() == "Поиск...":
  314. self.search_entry.delete(0, tk.END)
  315. self.search_entry.config(fg=self.colors['text'])
  316. def on_focus_out(event):
  317. if not self.search_entry.get():
  318. self.search_entry.insert(0, "Поиск...")
  319. self.search_entry.config(fg='#888888')
  320. self.search_entry.bind('<FocusIn>', on_focus_in)
  321. self.search_entry.bind('<FocusOut>', on_focus_out)
  322. def create_left_panel(self, parent):
  323. """Создает левую панель со списком сервисов"""
  324. # Заголовок
  325. title_frame = tk.Frame(parent, bg=self.colors['primary'])
  326. title_frame.pack(fill='x', padx=20, pady=(20, 10))
  327. tk.Label(title_frame, text="Ваши сервисы", font=self.heading_font,
  328. bg=self.colors['primary'], fg=self.colors['text']).pack(side='left')
  329. count_frame = tk.Frame(title_frame, bg=self.colors['accent'],
  330. relief='flat', bd=0)
  331. count_frame.pack(side='right', padx=(10, 0))
  332. self.count_label = tk.Label(count_frame, text="0", font=("Segoe UI", 10, "bold"),
  333. bg=self.colors['accent'], fg=self.colors['text'],
  334. padx=8, pady=2)
  335. self.count_label.pack()
  336. # Список с прокруткой
  337. list_frame = tk.Frame(parent, bg=self.colors['primary'])
  338. list_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20))
  339. # Создаем Canvas и Scrollbar
  340. canvas = tk.Canvas(list_frame, bg=self.colors['primary'],
  341. highlightthickness=0)
  342. scrollbar = ttk.Scrollbar(list_frame, orient="vertical",
  343. command=canvas.yview)
  344. self.scrollable_frame = tk.Frame(canvas, bg=self.colors['primary'])
  345. self.scrollable_frame.bind(
  346. "<Configure>",
  347. lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
  348. )
  349. canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
  350. canvas.configure(yscrollcommand=scrollbar.set)
  351. # Упаковываем
  352. canvas.pack(side="left", fill="both", expand=True)
  353. scrollbar.pack(side="right", fill="y")
  354. # Кнопки действий
  355. button_frame = tk.Frame(parent, bg=self.colors['primary'])
  356. button_frame.pack(fill='x', padx=20, pady=(0, 20))
  357. actions = [
  358. ("➕ Добавить", self.add_password, self.colors['success']),
  359. ("✏️ Редактировать", self.update_password, self.colors['accent']),
  360. ("🗑️ Удалить", self.delete_password, self.colors['warning'])
  361. ]
  362. for text, command, color in actions:
  363. btn = tk.Button(button_frame, text=text, font=self.body_font,
  364. bg=color, fg=self.colors['text'],
  365. command=command, relief='flat',
  366. padx=20, pady=10)
  367. btn.pack(side='left', expand=True, fill='x', padx=(0, 10))
  368. btn.bind("<Enter>", lambda e, b=btn: b.config(bg=self.colors['hover']))
  369. btn.bind("<Leave>", lambda e, b=btn, c=color: b.config(bg=c))
  370. def create_right_panel(self, parent):
  371. """Создает правую панель с редактором"""
  372. # Заголовок
  373. title_frame = tk.Frame(parent, bg=self.colors['primary'])
  374. title_frame.pack(fill='x', padx=20, pady=(20, 10))
  375. self.editor_title = tk.Label(title_frame, text="Новая запись",
  376. font=self.heading_font,
  377. bg=self.colors['primary'], fg=self.colors['text'])
  378. self.editor_title.pack(side='left')
  379. # Карточка формы
  380. form_card = tk.Frame(parent, bg=self.colors['card_bg'],
  381. relief='flat', bd=0)
  382. form_card.pack(fill='both', expand=True, padx=20, pady=(0, 20))
  383. # Поля формы
  384. fields = [
  385. ("🌐 Сервис/Сайт", "service"),
  386. ("👤 Логин/Email", "username"),
  387. ("🔑 Пароль", "password"),
  388. ("📝 Заметки", "notes")
  389. ]
  390. self.entries = {}
  391. for i, (label_text, field_name) in enumerate(fields):
  392. frame = tk.Frame(form_card, bg=self.colors['card_bg'])
  393. frame.pack(fill='x', padx=30, pady=15)
  394. # Метка
  395. tk.Label(frame, text=label_text, font=self.body_font,
  396. bg=self.colors['card_bg'], fg=self.colors['text'],
  397. width=15, anchor='w').pack(side='left')
  398. if field_name == "notes":
  399. # Многострочное поле для заметок
  400. entry_frame = tk.Frame(frame, bg=self.colors['entry_bg'])
  401. entry_frame.pack(side='left', fill='both', expand=True, padx=(10, 0))
  402. entry = scrolledtext.ScrolledText(entry_frame,
  403. bg=self.colors['entry_bg'],
  404. fg=self.colors['text'],
  405. insertbackground=self.colors['text'],
  406. font=self.body_font,
  407. relief='flat',
  408. height=4)
  409. entry.pack(fill='both', expand=True, padx=5, pady=5)
  410. elif field_name == "password":
  411. # Поле пароля с кнопками
  412. entry_frame = tk.Frame(frame, bg=self.colors['entry_bg'])
  413. entry_frame.pack(side='left', fill='x', expand=True, padx=(10, 0))
  414. self.password_var = tk.StringVar()
  415. entry = tk.Entry(entry_frame, textvariable=self.password_var,
  416. bg=self.colors['entry_bg'], fg=self.colors['text'],
  417. insertbackground=self.colors['text'],
  418. font=self.mono_font,
  419. relief='flat', show="●")
  420. entry.pack(side='left', fill='x', expand=True, padx=(5, 0), pady=5, ipady=5)
  421. # Кнопки управления паролем
  422. btn_frame = tk.Frame(entry_frame, bg=self.colors['entry_bg'])
  423. btn_frame.pack(side='right', padx=(5, 5))
  424. buttons = [
  425. ("👁", self.toggle_password_visibility),
  426. ("📋", self.copy_password),
  427. ("🎲", self.generate_password)
  428. ]
  429. for btn_text, btn_command in buttons:
  430. btn = tk.Button(btn_frame, text=btn_text, font=self.body_font,
  431. bg=self.colors['secondary'], fg=self.colors['text'],
  432. command=btn_command, relief='flat',
  433. width=3)
  434. btn.pack(side='left', padx=2)
  435. btn.bind("<Enter>", lambda e, b=btn: b.config(bg=self.colors['hover']))
  436. btn.bind("<Leave>", lambda e, b=btn: b.config(bg=self.colors['secondary']))
  437. else:
  438. # Обычное поле ввода
  439. entry_frame = tk.Frame(frame, bg=self.colors['entry_bg'])
  440. entry_frame.pack(side='left', fill='x', expand=True, padx=(10, 0))
  441. entry = tk.Entry(entry_frame, bg=self.colors['entry_bg'],
  442. fg=self.colors['text'],
  443. insertbackground=self.colors['text'],
  444. font=self.body_font,
  445. relief='flat')
  446. entry.pack(fill='x', expand=True, padx=5, pady=5, ipady=5)
  447. self.entries[field_name] = entry
  448. # Кнопки сохранения
  449. button_frame = tk.Frame(form_card, bg=self.colors['card_bg'])
  450. button_frame.pack(fill='x', padx=30, pady=(20, 30))
  451. save_btn = tk.Button(button_frame, text="💾 Сохранить",
  452. font=self.heading_font,
  453. bg=self.colors['success'], fg=self.colors['text'],
  454. command=self.save_current, relief='flat',
  455. padx=40, pady=12)
  456. save_btn.pack(side='left')
  457. save_btn.bind("<Enter>", lambda e: save_btn.config(bg=self.colors['hover']))
  458. save_btn.bind("<Leave>", lambda e: save_btn.config(bg=self.colors['success']))
  459. clear_btn = tk.Button(button_frame, text="🔄 Очистить",
  460. font=self.heading_font,
  461. bg=self.colors['secondary'], fg=self.colors['text'],
  462. command=self.clear_form, relief='flat',
  463. padx=40, pady=12)
  464. clear_btn.pack(side='left', padx=(20, 0))
  465. clear_btn.bind("<Enter>", lambda e: clear_btn.config(bg=self.colors['hover']))
  466. clear_btn.bind("<Leave>", lambda e: clear_btn.config(bg=self.colors['secondary']))
  467. def create_footer(self):
  468. """Создает нижнюю панель"""
  469. footer = tk.Frame(self.root, bg=self.colors['primary'], height=60)
  470. footer.pack(fill='x', side='bottom', padx=20, pady=(0, 20))
  471. footer.pack_propagate(False)
  472. # Левая часть - информация
  473. left_info = tk.Frame(footer, bg=self.colors['primary'])
  474. left_info.pack(side='left', padx=20)
  475. stats_text = f"🛡️ Защищено записей: {len(self.passwords)}"
  476. self.stats_label = tk.Label(left_info, text=stats_text, font=self.body_font,
  477. bg=self.colors['primary'], fg=self.colors['text'])
  478. self.stats_label.pack(side='left')
  479. # Правая часть - кнопки
  480. right_buttons = tk.Frame(footer, bg=self.colors['primary'])
  481. right_buttons.pack(side='right', padx=20)
  482. export_btn = tk.Button(right_buttons, text="📄 Экспорт в TXT",
  483. font=self.body_font,
  484. bg=self.colors['accent'], fg=self.colors['text'],
  485. command=self.export_to_txt, relief='flat',
  486. padx=20, pady=8)
  487. export_btn.pack(side='left', padx=(0, 10))
  488. export_btn.bind("<Enter>", lambda e: export_btn.config(bg=self.colors['hover']))
  489. export_btn.bind("<Leave>", lambda e: export_btn.config(bg=self.colors['accent']))
  490. backup_btn = tk.Button(right_buttons, text="💾 Создать резервную копию",
  491. font=self.body_font,
  492. bg=self.colors['secondary'], fg=self.colors['text'],
  493. command=self.create_manual_backup, relief='flat',
  494. padx=20, pady=8)
  495. backup_btn.pack(side='left')
  496. backup_btn.bind("<Enter>", lambda e: backup_btn.config(bg=self.colors['hover']))
  497. backup_btn.bind("<Leave>", lambda e: backup_btn.config(bg=self.colors['secondary']))
  498. def create_manual_backup(self):
  499. """Создает ручную резервную копию"""
  500. try:
  501. self.save_passwords()
  502. messagebox.showinfo("Успех", "✅ Резервная копия создана!")
  503. except:
  504. messagebox.showerror("Ошибка", "Не удалось создать резервную копию")
  505. def update_service_list(self, search_term=None):
  506. """Обновляет список сервисов"""
  507. # Очищаем текущий список
  508. for widget in self.scrollable_frame.winfo_children():
  509. widget.destroy()
  510. services = list(self.passwords.keys())
  511. if search_term and search_term != "Поиск...":
  512. search_term = search_term.lower()
  513. services = [s for s in services if search_term in s.lower()]
  514. self.count_label.config(text=str(len(services)))
  515. self.stats_label.config(text=f"🛡️ Защищено записей: {len(self.passwords)}")
  516. if not services:
  517. empty_label = tk.Label(self.scrollable_frame,
  518. text="Нет сохраненных сервисов",
  519. font=self.body_font,
  520. bg=self.colors['primary'], fg='#888888',
  521. pady=20)
  522. empty_label.pack()
  523. return
  524. for service in sorted(services):
  525. self.create_service_card(service)
  526. def create_service_card(self, service):
  527. """Создает карточку сервиса"""
  528. card = tk.Frame(self.scrollable_frame, bg=self.colors['card_bg'],
  529. relief='flat', bd=0)
  530. card.pack(fill='x', pady=5, padx=5)
  531. # Основное содержимое карточки
  532. content_frame = tk.Frame(card, bg=self.colors['card_bg'])
  533. content_frame.pack(fill='x', padx=15, pady=10)
  534. # Иконка (первая буква сервиса)
  535. icon_frame = tk.Frame(content_frame, bg=self.colors['accent'],
  536. width=40, height=40)
  537. icon_frame.pack(side='left')
  538. icon_frame.pack_propagate(False)
  539. icon_label = tk.Label(icon_frame, text=service[0].upper(),
  540. font=("Segoe UI", 16, "bold"),
  541. bg=self.colors['accent'], fg=self.colors['text'])
  542. icon_label.pack(expand=True)
  543. # Информация
  544. info_frame = tk.Frame(content_frame, bg=self.colors['card_bg'])
  545. info_frame.pack(side='left', fill='x', expand=True, padx=(15, 0))
  546. # Название сервиса
  547. name_label = tk.Label(info_frame, text=service,
  548. font=self.heading_font,
  549. bg=self.colors['card_bg'], fg=self.colors['text'],
  550. anchor='w')
  551. name_label.pack(fill='x')
  552. # Логин
  553. if service in self.passwords:
  554. username = self.passwords[service]['username']
  555. user_label = tk.Label(info_frame, text=f"👤 {username}",
  556. font=self.body_font,
  557. bg=self.colors['card_bg'], fg='#AAAAAA',
  558. anchor='w')
  559. user_label.pack(fill='x', pady=(2, 0))
  560. # Привязываем клик
  561. card.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  562. content_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  563. icon_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  564. info_frame.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  565. name_label.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  566. if 'user_label' in locals():
  567. user_label.bind("<Button-1>", lambda e, s=service: self.select_service(s))
  568. # Эффект при наведении
  569. def on_enter(e):
  570. card.config(bg=self.colors['hover'])
  571. content_frame.config(bg=self.colors['hover'])
  572. info_frame.config(bg=self.colors['hover'])
  573. name_label.config(bg=self.colors['hover'])
  574. if 'user_label' in locals():
  575. user_label.config(bg=self.colors['hover'])
  576. def on_leave(e):
  577. card.config(bg=self.colors['card_bg'])
  578. content_frame.config(bg=self.colors['card_bg'])
  579. info_frame.config(bg=self.colors['card_bg'])
  580. name_label.config(bg=self.colors['card_bg'])
  581. if 'user_label' in locals():
  582. user_label.config(bg=self.colors['card_bg'])
  583. card.bind("<Enter>", on_enter)
  584. card.bind("<Leave>", on_leave)
  585. content_frame.bind("<Enter>", on_enter)
  586. content_frame.bind("<Leave>", on_leave)
  587. def select_service(self, service):
  588. """Выбирает сервис из списка"""
  589. if service in self.passwords:
  590. data = self.passwords[service]
  591. # Обновляем заголовок
  592. self.editor_title.config(text=f"Редактирование: {service}")
  593. # Заполняем поля
  594. for widget in self.scrollable_frame.winfo_children():
  595. if hasattr(widget, 'selected'):
  596. widget.config(bg=self.colors['card_bg'])
  597. self.entries['service'].delete(0, tk.END)
  598. self.entries['service'].insert(0, service)
  599. self.entries['username'].delete(0, tk.END)
  600. self.entries['username'].insert(0, data['username'])
  601. password = self.decrypt(data['password'])
  602. self.password_var.set(password)
  603. self.entries['notes'].delete('1.0', tk.END)
  604. self.entries['notes'].insert('1.0', data['notes'])
  605. def on_search(self, event=None):
  606. """Обрабатывает поиск"""
  607. search_term = self.search_var.get()
  608. if search_term == "Поиск...":
  609. search_term = ""
  610. self.update_service_list(search_term)
  611. def add_password(self):
  612. """Добавляет новую запись"""
  613. self.clear_form()
  614. self.editor_title.config(text="Новая запись")
  615. self.entries['service'].focus()
  616. def clear_form(self):
  617. """Очищает форму"""
  618. self.editor_title.config(text="Новая запись")
  619. self.entries['service'].delete(0, tk.END)
  620. self.entries['username'].delete(0, tk.END)
  621. self.password_var.set("")
  622. self.entries['notes'].delete('1.0', tk.END)
  623. # Сбрасываем выделение в списке
  624. for widget in self.scrollable_frame.winfo_children():
  625. if hasattr(widget, 'selected'):
  626. widget.config(bg=self.colors['card_bg'])
  627. def save_current(self):
  628. """Сохраняет текущую запись"""
  629. service = self.entries['service'].get().strip()
  630. username = self.entries['username'].get().strip()
  631. password = self.password_var.get().strip()
  632. if not service:
  633. messagebox.showerror("Ошибка", "Введите название сервиса")
  634. self.entries['service'].focus()
  635. return
  636. if not username:
  637. messagebox.showerror("Ошибка", "Введите логин или email")
  638. self.entries['username'].focus()
  639. return
  640. if not password:
  641. messagebox.showerror("Ошибка", "Введите пароль")
  642. self.entries['password'].focus()
  643. return
  644. notes = self.entries['notes'].get('1.0', tk.END).strip()
  645. # Шифруем пароль
  646. encrypted_password = self.encrypt(password)
  647. self.passwords[service] = {
  648. 'username': username,
  649. 'password': encrypted_password,
  650. 'notes': notes
  651. }
  652. if self.save_passwords():
  653. self.update_service_list(self.search_var.get() if self.search_var.get() != "Поиск..." else None)
  654. # Показываем анимацию успеха
  655. self.show_success_animation("Запись сохранена!")
  656. def update_password(self):
  657. """Обновляет выбранную запись"""
  658. service = self.entries['service'].get().strip()
  659. if not service or service not in self.passwords:
  660. messagebox.showwarning("Внимание", "Выберите запись для редактирования")
  661. return
  662. self.save_current()
  663. def delete_password(self):
  664. """Удаляет выбранную запись"""
  665. service = self.entries['service'].get().strip()
  666. if not service or service not in self.passwords:
  667. messagebox.showwarning("Внимание", "Выберите запись для удаления")
  668. return
  669. if messagebox.askyesno("Подтверждение",
  670. f"Вы уверены, что хотите удалить запись '{service}'?",
  671. icon='warning'):
  672. del self.passwords[service]
  673. self.save_passwords()
  674. self.update_service_list(self.search_var.get() if self.search_var.get() != "Поиск..." else None)
  675. self.clear_form()
  676. self.show_success_animation("Запись удалена!")
  677. def toggle_password_visibility(self):
  678. """Показывает/скрывает пароль"""
  679. current_state = self.entries['password'].cget('show')
  680. if current_state == "●":
  681. self.entries['password'].config(show="")
  682. else:
  683. self.entries['password'].config(show="●")
  684. def copy_password(self):
  685. """Копирует пароль в буфер обмена"""
  686. password = self.password_var.get()
  687. if password:
  688. pyperclip.copy(password)
  689. self.show_temp_message("Пароль скопирован!")
  690. def generate_password(self):
  691. """Генерирует случайный пароль"""
  692. length = 16
  693. chars = string.ascii_letters + string.digits + "!@#$%^&*"
  694. password = ''.join(random.choice(chars) for _ in range(length))
  695. self.password_var.set(password)
  696. self.show_temp_message("Пароль сгенерирован!")
  697. def show_success_animation(self, message):
  698. """Показывает анимацию успеха"""
  699. # Можно добавить более сложную анимацию
  700. messagebox.showinfo("Успех", f"✅ {message}")
  701. def show_temp_message(self, message):
  702. """Показывает временное сообщение"""
  703. # Можно реализовать тост-уведомление
  704. print(f"📢 {message}")
  705. def animate_welcome(self):
  706. """Простая анимация приветствия"""
  707. # Можно добавить анимацию появления элементов
  708. pass
  709. def main():
  710. root = tk.Tk()
  711. # Проверяем библиотеки
  712. try:
  713. import cryptography
  714. import pyperclip
  715. except ImportError:
  716. # Простое окно с ошибкой
  717. error_root = tk.Tk()
  718. error_root.title("Ошибка")
  719. error_root.geometry("400x200")
  720. tk.Label(error_root, text="❌ Необходимо установить библиотеки:",
  721. font=("Segoe UI", 14)).pack(pady=20)
  722. tk.Label(error_root, text="pip install cryptography pyperclip",
  723. font=("Consolas", 12)).pack(pady=10)
  724. tk.Button(error_root, text="Выход", command=error_root.destroy,
  725. padx=20, pady=10).pack(pady=20)
  726. error_root.mainloop()
  727. return
  728. # Запускаем приложение
  729. app = ModernPasswordManager(root)
  730. root.mainloop()
  731. if __name__ == "__main__":
  732. main()