GUI-Version mit Tkinter hinzugefügt
- gui.py: Grafische Benutzeroberfläche - start_gui.sh/cmd: Startskripte für GUI - GUI_README.md: Installationsanleitung für Tkinter - Features: PDF-Verwaltung, Fortschrittsanzeige, Live-Log
This commit is contained in:
73
GUI_README.md
Normal file
73
GUI_README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 🎨 GUI Installation
|
||||||
|
|
||||||
|
Die grafische Benutzeroberfläche benötigt Tkinter, das auf manchen Systemen separat installiert werden muss.
|
||||||
|
|
||||||
|
## Installation von Tkinter
|
||||||
|
|
||||||
|
### Ubuntu/Debian
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install python3-tk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora/RHEL
|
||||||
|
```bash
|
||||||
|
sudo dnf install python3-tkinter
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
```bash
|
||||||
|
sudo pacman -S tk
|
||||||
|
```
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
Tkinter ist bereits mit Python installiert - nichts zu tun! ✓
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
Tkinter ist bereits mit Python installiert - nichts zu tun! ✓
|
||||||
|
|
||||||
|
## GUI starten
|
||||||
|
|
||||||
|
Nach der Tkinter-Installation:
|
||||||
|
|
||||||
|
**Linux/macOS:**
|
||||||
|
```bash
|
||||||
|
./start_gui.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```
|
||||||
|
Doppelklick auf start_gui.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
## GUI-Features
|
||||||
|
|
||||||
|
✨ **Drag & Drop-Alternative:** Klicken Sie auf "PDF hinzufügen"
|
||||||
|
📋 **Mehrere PDFs:** Wählen Sie mehrere Dateien gleichzeitig
|
||||||
|
📁 **Ausgabe-Verzeichnis:** Wählen Sie, wo die ICS-Dateien gespeichert werden
|
||||||
|
📊 **Echtzeit-Log:** Sehen Sie den Fortschritt live
|
||||||
|
✅ **Fortschrittsbalken:** Visuelles Feedback bei der Konvertierung
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### "No module named 'tkinter'"
|
||||||
|
→ Tkinter muss installiert werden (siehe oben)
|
||||||
|
|
||||||
|
### GUI startet nicht
|
||||||
|
→ Versuchen Sie:
|
||||||
|
```bash
|
||||||
|
rm -rf .venv
|
||||||
|
./start_gui.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fenster erscheint nicht
|
||||||
|
→ Stellen Sie sicher, dass Sie eine grafische Oberfläche haben (kein SSH ohne X11)
|
||||||
|
|
||||||
|
## Alternative: CLI-Version
|
||||||
|
|
||||||
|
Falls Tkinter nicht installiert werden kann, nutzen Sie die CLI-Version:
|
||||||
|
```bash
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Die CLI-Version funktioniert überall ohne zusätzliche Installation! 🚀
|
||||||
323
gui.py
Normal file
323
gui.py
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GUI für PDF zu ICS Konverter
|
||||||
|
Grafische Benutzeroberfläche mit Tkinter
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||||
|
from pathlib import Path
|
||||||
|
import threading
|
||||||
|
from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan
|
||||||
|
|
||||||
|
|
||||||
|
class PDFtoICSGUI:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("PDF zu ICS Konverter - Dienstplan Importer")
|
||||||
|
self.root.geometry("800x600")
|
||||||
|
self.root.minsize(700, 500)
|
||||||
|
|
||||||
|
# Variablen
|
||||||
|
self.pdf_files = []
|
||||||
|
self.output_dir = tk.StringVar(value=str(Path.cwd()))
|
||||||
|
|
||||||
|
# UI erstellen
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""Erstelle die UI-Komponenten"""
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header_frame = tk.Frame(self.root, bg="#2c3e50", height=80)
|
||||||
|
header_frame.pack(fill=tk.X)
|
||||||
|
header_frame.pack_propagate(False)
|
||||||
|
|
||||||
|
title_label = tk.Label(
|
||||||
|
header_frame,
|
||||||
|
text="📅 PDF zu ICS Konverter",
|
||||||
|
font=("Arial", 20, "bold"),
|
||||||
|
bg="#2c3e50",
|
||||||
|
fg="white"
|
||||||
|
)
|
||||||
|
title_label.pack(pady=20)
|
||||||
|
|
||||||
|
# Main Content Frame
|
||||||
|
content_frame = tk.Frame(self.root, padx=20, pady=20)
|
||||||
|
content_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# PDF-Dateien Bereich
|
||||||
|
pdf_label = tk.Label(
|
||||||
|
content_frame,
|
||||||
|
text="PDF-Dateien:",
|
||||||
|
font=("Arial", 12, "bold")
|
||||||
|
)
|
||||||
|
pdf_label.pack(anchor=tk.W, pady=(0, 5))
|
||||||
|
|
||||||
|
# Listbox mit Scrollbar für PDFs
|
||||||
|
list_frame = tk.Frame(content_frame)
|
||||||
|
list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||||
|
|
||||||
|
scrollbar = tk.Scrollbar(list_frame)
|
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
self.pdf_listbox = tk.Listbox(
|
||||||
|
list_frame,
|
||||||
|
selectmode=tk.EXTENDED,
|
||||||
|
yscrollcommand=scrollbar.set,
|
||||||
|
font=("Arial", 10)
|
||||||
|
)
|
||||||
|
self.pdf_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
scrollbar.config(command=self.pdf_listbox.yview)
|
||||||
|
|
||||||
|
# Buttons für PDF-Verwaltung
|
||||||
|
button_frame = tk.Frame(content_frame)
|
||||||
|
button_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
|
|
||||||
|
add_btn = tk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="➕ PDF hinzufügen",
|
||||||
|
command=self.add_pdf_files,
|
||||||
|
bg="#3498db",
|
||||||
|
fg="white",
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
padx=20,
|
||||||
|
pady=8,
|
||||||
|
cursor="hand2"
|
||||||
|
)
|
||||||
|
add_btn.pack(side=tk.LEFT, padx=(0, 5))
|
||||||
|
|
||||||
|
remove_btn = tk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="➖ Entfernen",
|
||||||
|
command=self.remove_selected_pdfs,
|
||||||
|
bg="#e74c3c",
|
||||||
|
fg="white",
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
padx=20,
|
||||||
|
pady=8,
|
||||||
|
cursor="hand2"
|
||||||
|
)
|
||||||
|
remove_btn.pack(side=tk.LEFT, padx=(0, 5))
|
||||||
|
|
||||||
|
clear_btn = tk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="🗑️ Alle entfernen",
|
||||||
|
command=self.clear_all_pdfs,
|
||||||
|
bg="#95a5a6",
|
||||||
|
fg="white",
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
padx=20,
|
||||||
|
pady=8,
|
||||||
|
cursor="hand2"
|
||||||
|
)
|
||||||
|
clear_btn.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Ausgabe-Verzeichnis
|
||||||
|
output_frame = tk.Frame(content_frame)
|
||||||
|
output_frame.pack(fill=tk.X, pady=(10, 10))
|
||||||
|
|
||||||
|
output_label = tk.Label(
|
||||||
|
output_frame,
|
||||||
|
text="Ausgabe-Verzeichnis:",
|
||||||
|
font=("Arial", 10)
|
||||||
|
)
|
||||||
|
output_label.pack(side=tk.LEFT, padx=(0, 5))
|
||||||
|
|
||||||
|
output_entry = tk.Entry(
|
||||||
|
output_frame,
|
||||||
|
textvariable=self.output_dir,
|
||||||
|
font=("Arial", 10),
|
||||||
|
state="readonly"
|
||||||
|
)
|
||||||
|
output_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
|
||||||
|
|
||||||
|
browse_btn = tk.Button(
|
||||||
|
output_frame,
|
||||||
|
text="📁 Durchsuchen",
|
||||||
|
command=self.browse_output_dir,
|
||||||
|
bg="#16a085",
|
||||||
|
fg="white",
|
||||||
|
font=("Arial", 10),
|
||||||
|
padx=15,
|
||||||
|
pady=5,
|
||||||
|
cursor="hand2"
|
||||||
|
)
|
||||||
|
browse_btn.pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Log-Bereich
|
||||||
|
log_label = tk.Label(
|
||||||
|
content_frame,
|
||||||
|
text="Status:",
|
||||||
|
font=("Arial", 10, "bold")
|
||||||
|
)
|
||||||
|
log_label.pack(anchor=tk.W, pady=(10, 5))
|
||||||
|
|
||||||
|
self.log_text = scrolledtext.ScrolledText(
|
||||||
|
content_frame,
|
||||||
|
height=8,
|
||||||
|
font=("Consolas", 9),
|
||||||
|
bg="#f8f9fa",
|
||||||
|
state=tk.DISABLED
|
||||||
|
)
|
||||||
|
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Konvertieren Button (groß und zentral)
|
||||||
|
convert_btn = tk.Button(
|
||||||
|
content_frame,
|
||||||
|
text="🔄 PDFs konvertieren",
|
||||||
|
command=self.convert_pdfs,
|
||||||
|
bg="#27ae60",
|
||||||
|
fg="white",
|
||||||
|
font=("Arial", 14, "bold"),
|
||||||
|
padx=30,
|
||||||
|
pady=15,
|
||||||
|
cursor="hand2"
|
||||||
|
)
|
||||||
|
convert_btn.pack(pady=20)
|
||||||
|
|
||||||
|
# Fortschrittsbalken
|
||||||
|
self.progress = ttk.Progressbar(
|
||||||
|
content_frame,
|
||||||
|
mode='determinate',
|
||||||
|
length=300
|
||||||
|
)
|
||||||
|
self.progress.pack(pady=(0, 10))
|
||||||
|
|
||||||
|
# Initial log message
|
||||||
|
self.log("Bereit. Fügen Sie PDF-Dateien hinzu um zu starten.")
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
"""Füge eine Nachricht zum Log hinzu"""
|
||||||
|
self.log_text.config(state=tk.NORMAL)
|
||||||
|
self.log_text.insert(tk.END, message + "\n")
|
||||||
|
self.log_text.see(tk.END)
|
||||||
|
self.log_text.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def add_pdf_files(self):
|
||||||
|
"""Öffne Datei-Dialog zum Hinzufügen von PDFs"""
|
||||||
|
files = filedialog.askopenfilenames(
|
||||||
|
title="PDF-Dateien auswählen",
|
||||||
|
filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")]
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file not in self.pdf_files:
|
||||||
|
self.pdf_files.append(file)
|
||||||
|
self.pdf_listbox.insert(tk.END, Path(file).name)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
self.log(f"✓ {len(files)} PDF-Datei(en) hinzugefügt")
|
||||||
|
|
||||||
|
def remove_selected_pdfs(self):
|
||||||
|
"""Entferne ausgewählte PDFs aus der Liste"""
|
||||||
|
selected = self.pdf_listbox.curselection()
|
||||||
|
|
||||||
|
# Rückwärts durchlaufen, um Indexprobleme zu vermeiden
|
||||||
|
for index in reversed(selected):
|
||||||
|
self.pdf_listbox.delete(index)
|
||||||
|
del self.pdf_files[index]
|
||||||
|
|
||||||
|
if selected:
|
||||||
|
self.log(f"✓ {len(selected)} PDF-Datei(en) entfernt")
|
||||||
|
|
||||||
|
def clear_all_pdfs(self):
|
||||||
|
"""Entferne alle PDFs aus der Liste"""
|
||||||
|
count = len(self.pdf_files)
|
||||||
|
self.pdf_listbox.delete(0, tk.END)
|
||||||
|
self.pdf_files.clear()
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
self.log(f"✓ Alle {count} PDF-Datei(en) entfernt")
|
||||||
|
|
||||||
|
def browse_output_dir(self):
|
||||||
|
"""Öffne Dialog zur Auswahl des Ausgabe-Verzeichnisses"""
|
||||||
|
directory = filedialog.askdirectory(
|
||||||
|
title="Ausgabe-Verzeichnis auswählen",
|
||||||
|
initialdir=self.output_dir.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
if directory:
|
||||||
|
self.output_dir.set(directory)
|
||||||
|
self.log(f"✓ Ausgabe-Verzeichnis: {directory}")
|
||||||
|
|
||||||
|
def convert_pdfs(self):
|
||||||
|
"""Konvertiere alle PDFs zu ICS"""
|
||||||
|
if not self.pdf_files:
|
||||||
|
messagebox.showwarning(
|
||||||
|
"Keine PDFs",
|
||||||
|
"Bitte fügen Sie mindestens eine PDF-Datei hinzu."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Starte Konvertierung in separatem Thread
|
||||||
|
thread = threading.Thread(target=self._convert_worker, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _convert_worker(self):
|
||||||
|
"""Worker-Thread für Konvertierung"""
|
||||||
|
self.progress['maximum'] = len(self.pdf_files)
|
||||||
|
self.progress['value'] = 0
|
||||||
|
|
||||||
|
output_dir = Path(self.output_dir.get())
|
||||||
|
success_count = 0
|
||||||
|
|
||||||
|
self.log("\n" + "="*50)
|
||||||
|
self.log("🔄 Starte Konvertierung...")
|
||||||
|
self.log("="*50)
|
||||||
|
|
||||||
|
for i, pdf_path in enumerate(self.pdf_files, 1):
|
||||||
|
try:
|
||||||
|
self.log(f"\n[{i}/{len(self.pdf_files)}] Verarbeite: {Path(pdf_path).name}")
|
||||||
|
|
||||||
|
# Extrahiere Daten
|
||||||
|
dienstplan = extract_dienstplan_data(pdf_path)
|
||||||
|
|
||||||
|
# Zeige Informationen
|
||||||
|
self.log(f" ├─ Name: {dienstplan['vorname']} {dienstplan['name']}")
|
||||||
|
self.log(f" ├─ Personalnummer: {dienstplan['personalnummer']}")
|
||||||
|
self.log(f" ├─ Betriebshof: {dienstplan['betriebshof']}")
|
||||||
|
self.log(f" └─ Events gefunden: {len(dienstplan['events'])}")
|
||||||
|
|
||||||
|
if not dienstplan['events']:
|
||||||
|
self.log(" ⚠️ Warnung: Keine Events gefunden!")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Erstelle ICS-Datei
|
||||||
|
ics_filename = Path(pdf_path).stem + '.ics'
|
||||||
|
ics_path = output_dir / ics_filename
|
||||||
|
|
||||||
|
create_ics_from_dienstplan(dienstplan, str(ics_path))
|
||||||
|
|
||||||
|
self.log(f" ✓ ICS erstellt: {ics_filename}")
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f" ✗ Fehler: {str(e)}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.progress['value'] = i
|
||||||
|
|
||||||
|
# Zusammenfassung
|
||||||
|
self.log("\n" + "="*50)
|
||||||
|
self.log(f"✅ Fertig! {success_count}/{len(self.pdf_files)} ICS-Dateien erstellt")
|
||||||
|
self.log("="*50 + "\n")
|
||||||
|
|
||||||
|
# Erfolgsmeldung
|
||||||
|
if success_count > 0:
|
||||||
|
self.root.after(0, lambda: messagebox.showinfo(
|
||||||
|
"Konvertierung abgeschlossen",
|
||||||
|
f"Es wurden {success_count} ICS-Datei(en) erfolgreich erstellt!\n\n"
|
||||||
|
f"Speicherort: {output_dir}"
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion"""
|
||||||
|
root = tk.Tk()
|
||||||
|
app = PDFtoICSGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
34
start_gui.cmd
Normal file
34
start_gui.cmd
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@echo off
|
||||||
|
REM PDF zu ICS Konverter - GUI Startskript (Windows)
|
||||||
|
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
REM Wechsel ins Skriptverzeichnis
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
REM Überprüfe, ob venv existiert
|
||||||
|
if not exist ".venv" (
|
||||||
|
echo 📦 Python-Umgebung wird eingerichtet...
|
||||||
|
python3 -m venv .venv --upgrade-deps
|
||||||
|
if errorlevel 1 (
|
||||||
|
python -m venv .venv --upgrade-deps
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Überprüfe, ob Abhängigkeiten installiert sind
|
||||||
|
.venv\Scripts\python.exe -c "import pdfplumber" 2>nul
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo 📚 Installiere Abhängigkeiten...
|
||||||
|
call .venv\Scripts\python.exe -m pip install -q pdfplumber icalendar pypdf2 pytz
|
||||||
|
echo ✓ Abhängigkeiten installiert
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Starte die GUI
|
||||||
|
echo 🎨 Starte GUI...
|
||||||
|
call .venv\Scripts\pythonw.exe gui.py
|
||||||
|
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo ❌ Fehler beim Starten der GUI
|
||||||
|
pause
|
||||||
|
)
|
||||||
66
start_gui.sh
Executable file
66
start_gui.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# PDF zu ICS Konverter - GUI Startskript
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Finde Python-Executable
|
||||||
|
PYTHON_CMD=""
|
||||||
|
if command -v python3 &> /dev/null; then
|
||||||
|
PYTHON_CMD="python3"
|
||||||
|
elif command -v python &> /dev/null; then
|
||||||
|
PYTHON_CMD="python"
|
||||||
|
else
|
||||||
|
echo "❌ Fehler: Python nicht gefunden!"
|
||||||
|
echo "Bitte installieren Sie Python 3.6 oder höher."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🐍 Nutze: $PYTHON_CMD"
|
||||||
|
|
||||||
|
# Erstelle venv wenn nicht vorhanden
|
||||||
|
if [ ! -d ".venv" ]; then
|
||||||
|
echo "📦 Python-Umgebung wird eingerichtet..."
|
||||||
|
$PYTHON_CMD -m venv .venv --upgrade-deps || {
|
||||||
|
echo "❌ venv konnte nicht erstellt werden"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Nutze Python aus venv
|
||||||
|
PYTHON_VENV=".venv/bin/python"
|
||||||
|
|
||||||
|
# Überprüfe, ob Abhängigkeiten installiert sind
|
||||||
|
if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then
|
||||||
|
echo "📚 Installiere Abhängigkeiten..."
|
||||||
|
|
||||||
|
if $PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz 2>/dev/null; then
|
||||||
|
echo "✓ Abhängigkeiten installiert"
|
||||||
|
else
|
||||||
|
echo "❌ Installation fehlgeschlagen"
|
||||||
|
echo "🔧 Versuche venv neu aufzubauen..."
|
||||||
|
rm -rf .venv
|
||||||
|
$PYTHON_CMD -m venv .venv --upgrade-deps || {
|
||||||
|
echo "❌ venv konnte nicht neu erstellt werden"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz || {
|
||||||
|
echo "❌ Abhängigkeiten konnten nicht installiert werden"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "✓ venv neu erstellt"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Starte die GUI
|
||||||
|
echo "🎨 Starte GUI..."
|
||||||
|
if [ -f "$PYTHON_VENV" ]; then
|
||||||
|
$PYTHON_VENV gui.py
|
||||||
|
else
|
||||||
|
echo "❌ Fehler: Python-Umgebung ist beschädigt"
|
||||||
|
echo "📁 Bitte löschen Sie das .venv Verzeichnis und versuchen Sie erneut:"
|
||||||
|
echo " rm -rf .venv"
|
||||||
|
echo " ./start_gui.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user