Kein PW bei Export
Dieser Commit ist enthalten in:
@ -1,38 +1,13 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(curl:*)",
|
"Bash(python3:*)"
|
||||||
"Bash(nslookup:*)",
|
|
||||||
"WebFetch(domain:multilogin.com)",
|
|
||||||
"WebFetch(domain:dicloak.com)",
|
|
||||||
"WebFetch(domain:support.google.com)",
|
|
||||||
"Bash(python3 -m pip list:*)",
|
|
||||||
"Bash(python3:*)",
|
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(cat:*)",
|
|
||||||
"Bash(claude config)",
|
|
||||||
"Bash(claude config list:*)",
|
|
||||||
"Bash(claude mcp)",
|
|
||||||
"Bash(claude mcp:*)",
|
|
||||||
"Bash(sqlite3:*)",
|
|
||||||
"Bash(python -m pip install:*)",
|
|
||||||
"Bash(python -m pytest teststest_generator_tab_factory.py -v)",
|
|
||||||
"Bash(python tests:*)",
|
|
||||||
"Bash(python tests/test_generator_tab_factory.py)",
|
|
||||||
"Bash(git push:*)",
|
|
||||||
"Bash(git remote set-url:*)",
|
|
||||||
"WebSearch",
|
|
||||||
"Bash(find:*)",
|
|
||||||
"Bash(mount:*)",
|
|
||||||
"Read(//mnt/a/**)",
|
|
||||||
"Read(//mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/**)",
|
|
||||||
"Read(//mnt/c/Users/Administrator/**)",
|
|
||||||
"Bash(/mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/python.exe -c \"\nimport sys\nsys.dont_write_bytecode = True # Verhindere .pyc Erstellung\nimport utils.email_handler as eh\nhandler = eh.EmailHandler()\npw = handler.config[''imap_pass'']\nprint(f''Windows Python lädt Passwort: {pw[:4]}...{pw[-4:]}'')\nif ''GZsg'' in pw:\n print(''✅ NEUES Passwort wird geladen!'')\nelse:\n print(''❌ ALTES Passwort wird noch geladen!'')\n\")"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"defaultMode": "acceptEdits",
|
"defaultMode": "acceptEdits",
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"/mnt/a/GiTea/Styleguide"
|
"/mnt/a/GiTea/Styleguide"
|
||||||
]
|
],
|
||||||
|
"ask": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
CLAUDE.md
Normale Datei
0
CLAUDE.md
Normale Datei
@ -5,9 +5,9 @@
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
- **Path**: `A:\GiTea\AccountForger`
|
- **Path**: `A:\GiTea\AccountForger`
|
||||||
- **Files**: 1317 files
|
- **Files**: 1393 files
|
||||||
- **Size**: 240.4 MB
|
- **Size**: 257.7 MB
|
||||||
- **Last Modified**: 2025-11-09 20:55
|
- **Last Modified**: 2025-11-16 22:31
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@ -65,6 +65,7 @@ config/
|
|||||||
controllers/
|
controllers/
|
||||||
│ ├── account_controller.py
|
│ ├── account_controller.py
|
||||||
│ ├── main_controller.py
|
│ ├── main_controller.py
|
||||||
|
│ ├── profile_export_controller.py
|
||||||
│ ├── session_controller.py
|
│ ├── session_controller.py
|
||||||
│ ├── settings_controller.py
|
│ ├── settings_controller.py
|
||||||
│ └── platform_controllers/
|
│ └── platform_controllers/
|
||||||
@ -360,8 +361,8 @@ utils/
|
|||||||
│ ├── modal_test.py
|
│ ├── modal_test.py
|
||||||
│ ├── password_generator.py
|
│ ├── password_generator.py
|
||||||
│ ├── performance_monitor.py
|
│ ├── performance_monitor.py
|
||||||
│ ├── proxy_rotator.py
|
│ ├── process_guard.py
|
||||||
│ └── result_decorators.py
|
│ └── profile_export_service.py
|
||||||
views/
|
views/
|
||||||
├── about_dialog.py
|
├── about_dialog.py
|
||||||
├── main_window.py
|
├── main_window.py
|
||||||
@ -376,7 +377,9 @@ views/
|
|||||||
│ └── __init__.py
|
│ └── __init__.py
|
||||||
├── dialogs/
|
├── dialogs/
|
||||||
│ ├── account_creation_result_dialog.py
|
│ ├── account_creation_result_dialog.py
|
||||||
|
│ ├── export_success_dialog.py
|
||||||
│ ├── license_activation_dialog.py
|
│ ├── license_activation_dialog.py
|
||||||
|
│ ├── profile_export_dialog.py
|
||||||
│ └── __init__.py
|
│ └── __init__.py
|
||||||
├── tabs/
|
├── tabs/
|
||||||
│ ├── accounts_tab.py
|
│ ├── accounts_tab.py
|
||||||
@ -431,3 +434,5 @@ This project is managed with Claude Project Manager. To work with this project:
|
|||||||
- README updated on 2025-10-08 22:34:43
|
- README updated on 2025-10-08 22:34:43
|
||||||
- README updated on 2025-10-18 22:23:22
|
- README updated on 2025-10-18 22:23:22
|
||||||
- README updated on 2025-11-09 21:00:06
|
- README updated on 2025-11-09 21:00:06
|
||||||
|
- README updated on 2025-11-16 22:31:39
|
||||||
|
- README updated on 2025-11-16 22:32:35
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class ProfileExportController:
|
|||||||
logger.info(f"Export-Optionen: Formate={formats}, Passwort={password_protect}")
|
logger.info(f"Export-Optionen: Formate={formats}, Passwort={password_protect}")
|
||||||
|
|
||||||
# 3. Speicherort wählen
|
# 3. Speicherort wählen
|
||||||
save_directory = self._select_save_location(parent_widget, account_data, formats, password_protect)
|
save_directory = self._select_save_location(parent_widget, account_data, formats)
|
||||||
|
|
||||||
if not save_directory:
|
if not save_directory:
|
||||||
logger.info("Kein Speicherort ausgewählt - Export abgebrochen")
|
logger.info("Kein Speicherort ausgewählt - Export abgebrochen")
|
||||||
@ -79,11 +79,11 @@ class ProfileExportController:
|
|||||||
|
|
||||||
logger.info(f"Speicherort: {save_directory}")
|
logger.info(f"Speicherort: {save_directory}")
|
||||||
|
|
||||||
# 4. Export durchführen
|
# 4. Export durchführen (ohne Passwortschutz)
|
||||||
files_dict, password = self.export_service.export_account(
|
files_dict = self.export_service.export_account(
|
||||||
account_data,
|
account_data,
|
||||||
formats,
|
formats,
|
||||||
password_protect
|
password_protect=False
|
||||||
)
|
)
|
||||||
|
|
||||||
if not files_dict:
|
if not files_dict:
|
||||||
@ -117,8 +117,7 @@ class ProfileExportController:
|
|||||||
show_export_success(
|
show_export_success(
|
||||||
parent_widget,
|
parent_widget,
|
||||||
saved_files,
|
saved_files,
|
||||||
save_directory,
|
save_directory
|
||||||
password
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert")
|
logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert")
|
||||||
@ -136,12 +135,6 @@ class ProfileExportController:
|
|||||||
"Bitte installieren Sie die erforderlichen Bibliotheken:\n"
|
"Bitte installieren Sie die erforderlichen Bibliotheken:\n"
|
||||||
"pip install reportlab svglib"
|
"pip install reportlab svglib"
|
||||||
)
|
)
|
||||||
elif "pyzipper" in error_message.lower():
|
|
||||||
error_message = (
|
|
||||||
"Passwortgeschützter Export ist nicht verfügbar.\n\n"
|
|
||||||
"Bitte installieren Sie die erforderliche Bibliothek:\n"
|
|
||||||
"pip install pyzipper"
|
|
||||||
)
|
|
||||||
|
|
||||||
show_error(
|
show_error(
|
||||||
parent_widget,
|
parent_widget,
|
||||||
@ -154,62 +147,30 @@ class ProfileExportController:
|
|||||||
self,
|
self,
|
||||||
parent_widget,
|
parent_widget,
|
||||||
account_data: Dict[str, Any],
|
account_data: Dict[str, Any],
|
||||||
formats: list,
|
formats: list
|
||||||
password_protect: bool
|
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Öffnet einen Datei-Dialog zur Auswahl des Speicherorts.
|
Öffnet einen Verzeichnis-Dialog zur Auswahl des Speicherorts.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent_widget: Parent-Widget
|
parent_widget: Parent-Widget
|
||||||
account_data: Account-Daten
|
account_data: Account-Daten
|
||||||
formats: Liste der Export-Formate
|
formats: Liste der Export-Formate
|
||||||
password_protect: Ob Passwortschutz aktiviert ist
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Ausgewähltes Verzeichnis oder None bei Abbruch
|
Ausgewähltes Verzeichnis oder None bei Abbruch
|
||||||
"""
|
"""
|
||||||
# Standard-Dateiname generieren
|
|
||||||
if password_protect:
|
|
||||||
# Bei Passwortschutz wird eine ZIP erstellt
|
|
||||||
default_filename = self.export_service.generate_filename(
|
|
||||||
account_data,
|
|
||||||
"zip"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Ohne Passwortschutz: Ersten Format als Beispiel nehmen
|
|
||||||
first_format = formats[0] if formats else "csv"
|
|
||||||
default_filename = self.export_service.generate_filename(
|
|
||||||
account_data,
|
|
||||||
first_format
|
|
||||||
)
|
|
||||||
|
|
||||||
# Standard-Speicherort: Benutzer-Downloads-Ordner
|
# Standard-Speicherort: Benutzer-Downloads-Ordner
|
||||||
default_directory = str(Path.home() / "Downloads")
|
default_directory = str(Path.home() / "Downloads")
|
||||||
|
|
||||||
if password_protect:
|
# Verzeichnis-Dialog für mehrere Dateien
|
||||||
# Für ZIP: File-Dialog
|
directory = QFileDialog.getExistingDirectory(
|
||||||
file_path, _ = QFileDialog.getSaveFileName(
|
parent_widget,
|
||||||
parent_widget,
|
"Speicherort für Export auswählen",
|
||||||
"Profil exportieren",
|
default_directory
|
||||||
os.path.join(default_directory, default_filename),
|
)
|
||||||
"ZIP-Archiv (*.zip)"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not file_path:
|
return directory if directory else None
|
||||||
return None
|
|
||||||
|
|
||||||
# Verzeichnis aus Dateipfad extrahieren
|
|
||||||
return os.path.dirname(file_path)
|
|
||||||
else:
|
|
||||||
# Für mehrere Dateien: Verzeichnis-Dialog
|
|
||||||
directory = QFileDialog.getExistingDirectory(
|
|
||||||
parent_widget,
|
|
||||||
"Speicherort für Export auswählen",
|
|
||||||
default_directory
|
|
||||||
)
|
|
||||||
|
|
||||||
return directory if directory else None
|
|
||||||
|
|
||||||
def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool:
|
def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
Profil-Export-Service für Account-Daten
|
Profil-Export-Service für Account-Daten
|
||||||
|
|
||||||
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF)
|
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF).
|
||||||
mit optionalem Passwortschutz.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
import secrets
|
|
||||||
import string
|
import string
|
||||||
import logging
|
import logging
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger("profile_export_service")
|
logger = logging.getLogger("profile_export_service")
|
||||||
@ -287,88 +285,6 @@ class ProfileExportService:
|
|||||||
logger.error(f"Fehler beim PDF-Export: {e}")
|
logger.error(f"Fehler beim PDF-Export: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_password(length: int = 10) -> str:
|
|
||||||
"""
|
|
||||||
Generiert ein sicheres zufälliges Passwort.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
length: Länge des Passworts (Standard: 10)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generiertes Passwort
|
|
||||||
"""
|
|
||||||
# Zeichensatz: Groß- und Kleinbuchstaben, Zahlen, Sonderzeichen
|
|
||||||
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
|
||||||
|
|
||||||
# Sicherstellen dass mindestens ein Zeichen von jedem Typ vorhanden ist
|
|
||||||
password_chars = [
|
|
||||||
secrets.choice(string.ascii_uppercase), # Mindestens ein Großbuchstabe
|
|
||||||
secrets.choice(string.ascii_lowercase), # Mindestens ein Kleinbuchstabe
|
|
||||||
secrets.choice(string.digits), # Mindestens eine Zahl
|
|
||||||
secrets.choice("!@#$%^&*") # Mindestens ein Sonderzeichen
|
|
||||||
]
|
|
||||||
|
|
||||||
# Rest auffüllen
|
|
||||||
for _ in range(length - 4):
|
|
||||||
password_chars.append(secrets.choice(alphabet))
|
|
||||||
|
|
||||||
# Mischen für Zufälligkeit
|
|
||||||
secrets.SystemRandom().shuffle(password_chars)
|
|
||||||
|
|
||||||
password = ''.join(password_chars)
|
|
||||||
logger.info("Passwort generiert")
|
|
||||||
return password
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_protected_zip(files_dict: Dict[str, bytes], password: str) -> bytes:
|
|
||||||
"""
|
|
||||||
Erstellt eine passwortgeschützte ZIP-Datei.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
files_dict: Dictionary mit {filename: content} Paaren
|
|
||||||
password: Passwort für die ZIP-Datei
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ZIP-Daten als bytes
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import pyzipper
|
|
||||||
|
|
||||||
# ZIP-Buffer erstellen
|
|
||||||
buffer = BytesIO()
|
|
||||||
|
|
||||||
# ZIP mit AES-Verschlüsselung erstellen
|
|
||||||
with pyzipper.AESZipFile(
|
|
||||||
buffer,
|
|
||||||
'w',
|
|
||||||
compression=pyzipper.ZIP_DEFLATED,
|
|
||||||
encryption=pyzipper.WZ_AES
|
|
||||||
) as zf:
|
|
||||||
# Passwort setzen
|
|
||||||
zf.setpassword(password.encode('utf-8'))
|
|
||||||
|
|
||||||
# Dateien hinzufügen
|
|
||||||
for filename, content in files_dict.items():
|
|
||||||
zf.writestr(filename, content)
|
|
||||||
|
|
||||||
# Buffer-Wert holen
|
|
||||||
zip_content = buffer.getvalue()
|
|
||||||
buffer.close()
|
|
||||||
|
|
||||||
logger.info(f"Passwortgeschützte ZIP erstellt mit {len(files_dict)} Dateien")
|
|
||||||
return zip_content
|
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
logger.error(f"pyzipper nicht installiert: {e}")
|
|
||||||
raise Exception(
|
|
||||||
"ZIP mit Passwortschutz erfordert 'pyzipper' Library. "
|
|
||||||
"Bitte installieren Sie: pip install pyzipper"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Fehler beim Erstellen der geschützten ZIP: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_filename(
|
def generate_filename(
|
||||||
account_data: Dict[str, Any],
|
account_data: Dict[str, Any],
|
||||||
@ -427,19 +343,17 @@ class ProfileExportService:
|
|||||||
account_data: Dict[str, Any],
|
account_data: Dict[str, Any],
|
||||||
formats: List[str],
|
formats: List[str],
|
||||||
password_protect: bool = False
|
password_protect: bool = False
|
||||||
) -> Tuple[Dict[str, bytes], Optional[str]]:
|
) -> Dict[str, bytes]:
|
||||||
"""
|
"""
|
||||||
Exportiert Account-Daten in angegebene Formate.
|
Exportiert Account-Daten in angegebene Formate.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
account_data: Account-Daten zum Exportieren
|
account_data: Account-Daten zum Exportieren
|
||||||
formats: Liste von Formaten ["csv", "txt", "pdf"]
|
formats: Liste von Formaten ["csv", "txt", "pdf"]
|
||||||
password_protect: Ob Dateien passwortgeschützt werden sollen
|
password_protect: Wird ignoriert (für Rückwärtskompatibilität)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple von (files_dict, password)
|
Dict mit {filename: content} für alle Formate
|
||||||
- files_dict: {filename: content} für alle Formate
|
|
||||||
- password: Generiertes Passwort (None wenn nicht geschützt)
|
|
||||||
"""
|
"""
|
||||||
files_dict = {}
|
files_dict = {}
|
||||||
|
|
||||||
@ -464,16 +378,4 @@ class ProfileExportService:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
|
logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
|
||||||
|
|
||||||
# Passwortschutz wenn gewünscht
|
return files_dict
|
||||||
password = None
|
|
||||||
if password_protect and files_dict:
|
|
||||||
password = ProfileExportService.generate_password()
|
|
||||||
|
|
||||||
# Alle Dateien in ZIP packen
|
|
||||||
zip_filename = ProfileExportService.generate_filename(account_data, "zip")
|
|
||||||
zip_content = ProfileExportService.create_protected_zip(files_dict, password)
|
|
||||||
|
|
||||||
# Nur ZIP zurückgeben
|
|
||||||
files_dict = {zip_filename: zip_content}
|
|
||||||
|
|
||||||
return files_dict, password
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Erfolgs-Dialog für Profil-Export
|
Erfolgs-Dialog für Profil-Export
|
||||||
|
|
||||||
Zeigt exportierte Dateien und optional das generierte Passwort an.
|
Zeigt exportierte Dateien an.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -9,11 +9,11 @@ import logging
|
|||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||||
QFrame, QGraphicsDropShadowEffect, QApplication, QScrollArea, QWidget
|
QFrame, QGraphicsDropShadowEffect, QScrollArea, QWidget
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QFont, QColor
|
from PyQt5.QtGui import QFont, QColor
|
||||||
@ -28,8 +28,7 @@ class ExportSuccessDialog(QDialog):
|
|||||||
self,
|
self,
|
||||||
parent=None,
|
parent=None,
|
||||||
exported_files: List[str] = None,
|
exported_files: List[str] = None,
|
||||||
export_directory: str = "",
|
export_directory: str = ""
|
||||||
password: Optional[str] = None
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialisiert den Erfolgs-Dialog.
|
Initialisiert den Erfolgs-Dialog.
|
||||||
@ -38,12 +37,10 @@ class ExportSuccessDialog(QDialog):
|
|||||||
parent: Parent-Widget
|
parent: Parent-Widget
|
||||||
exported_files: Liste der exportierten Dateinamen
|
exported_files: Liste der exportierten Dateinamen
|
||||||
export_directory: Verzeichnis wo Dateien gespeichert wurden
|
export_directory: Verzeichnis wo Dateien gespeichert wurden
|
||||||
password: Optional generiertes Passwort (nur bei Passwortschutz)
|
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.exported_files = exported_files or []
|
self.exported_files = exported_files or []
|
||||||
self.export_directory = export_directory
|
self.export_directory = export_directory
|
||||||
self.password = password
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
@ -55,10 +52,8 @@ class ExportSuccessDialog(QDialog):
|
|||||||
self.setAttribute(Qt.WA_TranslucentBackground, True)
|
self.setAttribute(Qt.WA_TranslucentBackground, True)
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
|
|
||||||
# Dynamische Höhe basierend auf Inhalt
|
# Dynamische Höhe basierend auf Anzahl der Dateien
|
||||||
base_height = 280
|
base_height = 280
|
||||||
if self.password:
|
|
||||||
base_height += 80 # Extra Platz für Passwort-Anzeige
|
|
||||||
if len(self.exported_files) > 2:
|
if len(self.exported_files) > 2:
|
||||||
base_height += 20 * (len(self.exported_files) - 2) # Extra für mehr Dateien
|
base_height += 20 * (len(self.exported_files) - 2) # Extra für mehr Dateien
|
||||||
|
|
||||||
@ -153,72 +148,6 @@ class ExportSuccessDialog(QDialog):
|
|||||||
|
|
||||||
container_layout.addWidget(scroll_area)
|
container_layout.addWidget(scroll_area)
|
||||||
|
|
||||||
# Passwort-Anzeige (nur wenn Passwort vorhanden)
|
|
||||||
if self.password:
|
|
||||||
password_frame = QFrame()
|
|
||||||
password_frame.setStyleSheet("""
|
|
||||||
QFrame {
|
|
||||||
background-color: #FEF3C7;
|
|
||||||
border: 1px solid #FCD34D;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
password_layout = QVBoxLayout(password_frame)
|
|
||||||
password_layout.setSpacing(8)
|
|
||||||
|
|
||||||
# Warnung
|
|
||||||
warning_label = QLabel("⚠ Bitte Passwort speichern!")
|
|
||||||
warning_label.setStyleSheet("""
|
|
||||||
color: #92400E;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Passwort mit Copy-Button
|
|
||||||
password_row = QHBoxLayout()
|
|
||||||
password_row.setSpacing(10)
|
|
||||||
|
|
||||||
password_label = QLabel(f"🔒 Passwort: {self.password}")
|
|
||||||
password_label.setStyleSheet("""
|
|
||||||
color: #78350F;
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
""")
|
|
||||||
password_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
|
||||||
|
|
||||||
copy_button = QPushButton("Kopieren")
|
|
||||||
copy_button.setMaximumWidth(80)
|
|
||||||
copy_button.setMinimumHeight(28)
|
|
||||||
copy_button.setCursor(Qt.PointingHandCursor)
|
|
||||||
copy_button.setStyleSheet("""
|
|
||||||
QPushButton {
|
|
||||||
background-color: #F59E0B;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
QPushButton:hover {
|
|
||||||
background-color: #D97706;
|
|
||||||
}
|
|
||||||
QPushButton:pressed {
|
|
||||||
background-color: #B45309;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
copy_button.clicked.connect(self.copy_password)
|
|
||||||
|
|
||||||
password_row.addWidget(password_label, 1)
|
|
||||||
password_row.addWidget(copy_button)
|
|
||||||
|
|
||||||
password_layout.addWidget(warning_label)
|
|
||||||
password_layout.addLayout(password_row)
|
|
||||||
|
|
||||||
container_layout.addWidget(password_frame)
|
|
||||||
|
|
||||||
# Speicherort
|
# Speicherort
|
||||||
location_label = QLabel(f"Gespeichert in:")
|
location_label = QLabel(f"Gespeichert in:")
|
||||||
location_label.setStyleSheet("color: #6B7280; font-size: 12px;")
|
location_label.setStyleSheet("color: #6B7280; font-size: 12px;")
|
||||||
@ -299,30 +228,6 @@ class ExportSuccessDialog(QDialog):
|
|||||||
# Container zum Hauptlayout hinzufügen
|
# Container zum Hauptlayout hinzufügen
|
||||||
main_layout.addWidget(self.container)
|
main_layout.addWidget(self.container)
|
||||||
|
|
||||||
def copy_password(self):
|
|
||||||
"""Kopiert das Passwort in die Zwischenablage"""
|
|
||||||
if self.password:
|
|
||||||
clipboard = QApplication.clipboard()
|
|
||||||
clipboard.setText(self.password)
|
|
||||||
logger.info("Passwort in Zwischenablage kopiert")
|
|
||||||
|
|
||||||
# Kurzes visuelles Feedback
|
|
||||||
sender = self.sender()
|
|
||||||
if sender:
|
|
||||||
original_text = sender.text()
|
|
||||||
sender.setText("✓ Kopiert!")
|
|
||||||
sender.setStyleSheet(sender.styleSheet().replace("#F59E0B", "#10B981"))
|
|
||||||
|
|
||||||
# Nach 2 Sekunden zurücksetzen
|
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
QTimer.singleShot(2000, lambda: self._reset_copy_button(sender, original_text))
|
|
||||||
|
|
||||||
def _reset_copy_button(self, button, original_text):
|
|
||||||
"""Setzt den Copy-Button zurück"""
|
|
||||||
if button:
|
|
||||||
button.setText(original_text)
|
|
||||||
button.setStyleSheet(button.styleSheet().replace("#10B981", "#F59E0B"))
|
|
||||||
|
|
||||||
def open_folder(self):
|
def open_folder(self):
|
||||||
"""Öffnet den Export-Ordner im Datei-Explorer"""
|
"""Öffnet den Export-Ordner im Datei-Explorer"""
|
||||||
if not self.export_directory or not os.path.exists(self.export_directory):
|
if not self.export_directory or not os.path.exists(self.export_directory):
|
||||||
@ -373,8 +278,7 @@ class ExportSuccessDialog(QDialog):
|
|||||||
def show_export_success(
|
def show_export_success(
|
||||||
parent,
|
parent,
|
||||||
exported_files: List[str],
|
exported_files: List[str],
|
||||||
export_directory: str,
|
export_directory: str
|
||||||
password: Optional[str] = None
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Zeigt den Export-Erfolgs-Dialog modal an.
|
Zeigt den Export-Erfolgs-Dialog modal an.
|
||||||
@ -383,7 +287,6 @@ def show_export_success(
|
|||||||
parent: Parent-Widget
|
parent: Parent-Widget
|
||||||
exported_files: Liste der exportierten Dateinamen
|
exported_files: Liste der exportierten Dateinamen
|
||||||
export_directory: Verzeichnis wo Dateien gespeichert wurden
|
export_directory: Verzeichnis wo Dateien gespeichert wurden
|
||||||
password: Optional generiertes Passwort
|
|
||||||
"""
|
"""
|
||||||
dialog = ExportSuccessDialog(parent, exported_files, export_directory, password)
|
dialog = ExportSuccessDialog(parent, exported_files, export_directory)
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Export-Dialog für Account-Profile
|
Export-Dialog für Account-Profile
|
||||||
|
|
||||||
Ermöglicht Auswahl von Export-Formaten und Passwortschutz.
|
Ermöglicht Auswahl von Export-Formaten.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -153,36 +153,6 @@ class ProfileExportDialog(QDialog):
|
|||||||
|
|
||||||
container_layout.addWidget(format_group)
|
container_layout.addWidget(format_group)
|
||||||
|
|
||||||
# Passwortschutz-Checkbox
|
|
||||||
self.password_checkbox = QCheckBox("Mit Passwort schützen")
|
|
||||||
self.password_checkbox.setChecked(False)
|
|
||||||
self.password_checkbox.setStyleSheet("""
|
|
||||||
QCheckBox {
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: 'Poppins', sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #374151;
|
|
||||||
spacing: 8px;
|
|
||||||
}
|
|
||||||
QCheckBox::indicator {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 2px solid #D1D5DB;
|
|
||||||
}
|
|
||||||
QCheckBox::indicator:checked {
|
|
||||||
background-color: #10B981;
|
|
||||||
border-color: #10B981;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Info-Text für Passwortschutz
|
|
||||||
password_info = QLabel("(Wird automatisch generiert)")
|
|
||||||
password_info.setStyleSheet("color: #9CA3AF; font-size: 11px; font-family: 'Poppins', sans-serif; margin-left: 26px;")
|
|
||||||
|
|
||||||
container_layout.addWidget(self.password_checkbox)
|
|
||||||
container_layout.addWidget(password_info)
|
|
||||||
|
|
||||||
# Spacer
|
# Spacer
|
||||||
container_layout.addStretch()
|
container_layout.addStretch()
|
||||||
|
|
||||||
@ -271,8 +241,8 @@ class ProfileExportDialog(QDialog):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Passwortschutz-Option
|
# Passwortschutz ist entfernt - immer False
|
||||||
password_protect = self.password_checkbox.isChecked()
|
password_protect = False
|
||||||
|
|
||||||
# Signal emittieren
|
# Signal emittieren
|
||||||
self.export_confirmed.emit(selected_formats, password_protect)
|
self.export_confirmed.emit(selected_formats, password_protect)
|
||||||
@ -295,7 +265,8 @@ class ProfileExportDialog(QDialog):
|
|||||||
if self.pdf_checkbox.isChecked():
|
if self.pdf_checkbox.isChecked():
|
||||||
formats.append("pdf")
|
formats.append("pdf")
|
||||||
|
|
||||||
password_protect = self.password_checkbox.isChecked()
|
# Passwortschutz ist entfernt - immer False
|
||||||
|
password_protect = False
|
||||||
|
|
||||||
return formats, password_protect
|
return formats, password_protect
|
||||||
|
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren