all files uploaded

This commit is contained in:
Ben
2024-11-13 17:05:33 +01:00
committed by GitHub
parent f4dd6e1ba2
commit 0cee90a7b8
13 changed files with 1412 additions and 0 deletions

201
api.py Normal file
View File

@@ -0,0 +1,201 @@
from flask import Flask, request, jsonify, send_file, make_response
from flask_swagger_ui import get_swaggerui_blueprint
import os
import chnage # Dein Konvertierungsskript
from flask_cors import CORS
import shutil
import logging
from werkzeug.utils import secure_filename
import mimetypes
import secrets
import hashlib
from functools import wraps
app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
UPLOAD_FOLDER = 'uploads'
CONVERT_FOLDER = 'convert'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['CONVERT_FOLDER'] = CONVERT_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(CONVERT_FOLDER, exist_ok=True)
logging.basicConfig(level=logging.DEBUG)
SWAGGER_URL = '/docs'
API_URL = '/static/swagger.json'
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={
'app_name': "Convert-Commander API"
}
)
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
hashed_tokens = set()
def generate_token():
return secrets.token_urlsafe(32)
def hash_token(token):
"""Generiere einen SHA-256 Hash des Tokens."""
return hashlib.sha256(token.encode()).hexdigest()
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('X-API-Token')
if not token:
return jsonify({'error': 'API token is missing'}), 401
hashed_token = hash_token(token)
if hashed_token not in hashed_tokens:
return jsonify({'error': 'Invalid API token'}), 401
return f(*args, **kwargs)
return decorated
@app.route('/generate_token', methods=['POST'])
def create_token():
"""
Generiere ein neues API-Token
---
tags:
- Authentication
responses:
200:
description: New API token generated
"""
token = generate_token()
hashed_token = hash_token(token)
hashed_tokens.add(hashed_token) # Speichere das gehashte Token
return jsonify({'token': token}), 200 # Gebe das einfache Token zurück
def delete_files_in_folder(folder_path):
if os.path.exists(folder_path):
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
logging.error(f'Error deleting {file_path}. Reason: {e}')
else:
logging.warning(f'Folder {folder_path} does not exist')
@app.route('/upload', methods=['POST'])
@token_required
def upload_file():
"""
Lade eine Datei hoch und konvertiere sie in das angegebene Format
---
tags:
- File Conversion
consumes:
- multipart/form-data
parameters:
- in: header
name: X-API-Token
type: string
required: true
description: API token for authentication
- in: formData
name: file
type: file
required: true
description: The file to upload
- in: formData
name: format
type: string
required: true
description: The target format for conversion
responses:
200:
description: Successfully converted file
400:
description: Invalid request
401:
description: Unauthorized
500:
description: Server error
"""
if 'file' not in request.files or 'format' not in request.form:
return jsonify({'error': 'No file or format specified'}), 400
file = request.files['file']
target_format = request.form['format']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
filename = secure_filename(file.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
logging.info(f"File uploaded: {filepath}")
try:
logging.debug(f"Starting conversion from {filepath} to {target_format}")
chnage.start(filepath, target_format)
converted_filename = os.path.splitext(filename)[0] + f'.{target_format}'
converted_filepath = os.path.join(app.config['CONVERT_FOLDER'], converted_filename)
if not os.path.exists(converted_filepath):
return jsonify({'error': 'Converted file not found'}), 500
logging.debug(f"Sending converted file: {converted_filepath}")
mime_type, _ = mimetypes.guess_type(converted_filepath)
if mime_type is None:
mime_type = 'application/octet-stream'
with open(converted_filepath, 'rb') as f:
file_data = f.read()
response = make_response(file_data)
response.headers.set('Content-Type', mime_type)
response.headers.set('Content-Disposition', f'attachment; filename="{converted_filename}"')
response.headers.set('Content-Length', str(os.path.getsize(converted_filepath)))
logging.debug(f"Sent headers: {response.headers}")
return response
except Exception as e:
logging.error(f"Error during conversion: {str(e)}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/clear', methods=['POST'])
@token_required
def clear_folders():
"""
Leere alle Dateien in den Upload- und Konvertierungsordnern
---
tags:
- Maintenance
parameters:
- in: header
name: X-API-Token
type: string
required: true
description: API token for authentication
responses:
200:
description: Folders successfully cleared
401:
description: Unauthorized
"""
delete_files_in_folder(app.config['UPLOAD_FOLDER'])
delete_files_in_folder(app.config['CONVERT_FOLDER'])
return jsonify({'message': 'Folders cleared'}), 200
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", port="9596")

45
chnage.py Normal file
View File

@@ -0,0 +1,45 @@
import subprocess
import shutil
import os
def start(input_file, output_extension):
def convert_file(input_file, output_file):
if not os.path.exists(input_file):
print(f"Die Eingabedatei '{input_file}' existiert nicht.")
return
output_dir = os.path.dirname(output_file)
if not os.path.exists(output_dir):
print(f"Der Ausgabeordner '{output_dir}' existiert nicht.")
return
# Der LibreOffice-Befehl zum Konvertieren der Datei
command = [
'libreoffice',
'--headless',
'--convert-to', output_extension,
'--outdir', output_dir,
input_file
]
try:
result = subprocess.run(command, check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
print(f"Konvertierung erfolgreich abgeschlossen: {result.stdout.decode()}")
except subprocess.CalledProcessError as e:
print(f"Fehler beim Konvertieren: {e.stderr.decode()}")
print(f"Ausgabe: {e.stdout.decode()}")
# Dynamischer Zielpfad für das konvertierte Dokument
input_ext = os.path.splitext(input_file)[1].lstrip('.')
file_name = os.path.basename(input_file).replace(f'.{input_ext}', '')
output_file = os.path.join('convert', f'{file_name}.{output_extension}')
# Debug-Ausgabe für das Arbeitsverzeichnis
print(f"Arbeitsverzeichnis: {os.getcwd()}")
print(f"Ausgabeordner: {os.path.dirname(output_file)}")
convert_file(input_file, output_file)
print(f"Die Datei '{input_file}' wurde erfolgreich in '{output_file}' konvertiert.")

53
create-alias.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Start-Skript ausführbar machen
echo "Setting executable permission for start.sh..."
chmod +x start.sh
# Alias für Convert-Commander erstellen und laden
echo "Creating alias for Convert-Commander..."
echo "alias ccommander='./start.sh'" >> ~/.bash_aliases
source ~/.bash_aliases
# Bash-Completion für ccommander erstellen
echo "Setting up bash completion for ccommander..."
sudo mkdir -p /etc/bash_completion.d
# Completion-Skript erstellen
sudo tee /etc/bash_completion.d/ccommander-completion.bash > /dev/null << 'EOF'
_ccommander_completion() {
local cur prev opts sub_opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# Hauptoptionen
opts="web api"
# Unteroptionen basierend auf dem ersten Argument
case "${prev}" in
"web")
sub_opts="start stop status"
COMPREPLY=( $(compgen -W "${sub_opts}" -- ${cur}) )
return 0
;;
"api")
sub_opts="start stop status token"
COMPREPLY=( $(compgen -W "${sub_opts}" -- ${cur}) )
return 0
;;
"ccommander")
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
esac
}
complete -F _ccommander_completion ccommander
EOF
# Completion-Skript ausführbar machen und aktivieren
sudo chmod +x /etc/bash_completion.d/ccommander-completion.bash
source /etc/bash_completion.d/ccommander-completion.bash
echo "Installation completed. Bash completion for ccommander is now active."

109
index.py Normal file
View File

@@ -0,0 +1,109 @@
from flask import Flask, request, render_template, redirect, url_for, jsonify, send_file
import os
import chnage
from flask_cors import CORS
import shutil
from threading import Timer
app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Stelle sicher, dass der Upload-Ordner existiert
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Globale Variable zur Speicherung von filetest
global_filetest = None
folder_path_1 = 'uploads'
folder_path_2 = 'convert'
def delete_files_in_folder(folder_path):
# Überprüfen, ob der Ordner existiert
if os.path.exists(folder_path):
# Durch alle Dateien und Unterordner im Ordner iterieren
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
# Überprüfen, ob es eine Datei oder ein Ordner ist
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path) # Datei oder symbolischen Link löschen
elif os.path.isdir(file_path):
shutil.rmtree(file_path) # Ordner und dessen Inhalt löschen
except Exception as e:
print(f'Fehler beim Löschen {file_path}. Grund: {e}')
else:
print(f'Ordner {folder_path} existiert nicht')
def download_file(filepath, global_filetest):
filename = os.path.splitext(os.path.basename(filepath))[0]
filethepath = f'convert/{filename}.{global_filetest}'
try:
print(f"Bereit zum Download: {filethepath}")
return send_file(filethepath, as_attachment=True)
except Exception as e:
return str(e)
def delete_files_after_delay():
delete_files_in_folder(folder_path_1)
delete_files_in_folder(folder_path_2)
@app.route('/', methods=['GET', 'POST'])
def index():
global global_filetest
if request.method == 'POST':
if 'file' not in request.files:
return redirect(url_for('index', status='Keine Datei ausgewählt'))
file = request.files['file']
if file.filename == '':
return redirect(url_for('index', status='Keine Datei ausgewählt'))
if file and global_filetest is not None:
filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(filepath)
chnage.start(filepath, global_filetest)
response = redirect(url_for('download', filename=file.filename))
Timer(5, delete_files_after_delay).start()
return response
elif file:
return redirect(url_for('index', status='Datei hochgeladen, aber Dateityp nicht ausgewählt'))
return render_template('index.html', status=request.args.get('status'))
@app.route('/download/<filename>', methods=['GET'])
def download(filename):
global global_filetest
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
return download_file(filepath, global_filetest)
@app.route('/empfange_daten', methods=['POST'])
def empfange_daten():
global global_filetest
daten = request.json['daten']
global_filetest = daten
print(f"Empfangene Daten: {daten}")
return jsonify({"status": "erfolgreich empfangen", "message": "Bitte laden Sie jetzt eine Datei hoch"})
@app.route('/docs')
def doc():
return render_template("docs.html")
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")

121
install.sh Normal file
View File

@@ -0,0 +1,121 @@
#!/bin/bash
# Logo anzeigen
cat << "EOF"
_____ _ _____ _
/ ____| | | / ____| | |
| | ___ _ __ __ __ ___ _ __ | |_ ______ | | ___ _ __ ___ _ __ ___ __ _ _ __ __| | ___ _ __
| | / _ \ | '_ \ \ / // _ \| '__|| __||______|| | / _ \ | '_ ` _ \ | '_ ` _ \ / _` || '_ \ / _` | / _ \| '__|
| |____| (_) || | | |\ V /| __/| | | |_ | |____| (_) || | | | | || | | | | || (_| || | | || (_| || __/| |
\_____|\___/ |_| |_| \_/ \___||_| \__| \_____|\___/ |_| |_| |_||_| |_| |_| \__,_||_| |_| \__,_| \___||_|
EOF
echo "Starting Convert-Commander installation..."
# Funktion zur Fortschrittsanzeige
progress_bar() {
local progress=$1
local total=12
local percent=$(( progress * 100 / total ))
local completed=$(( percent / 5 ))
local remaining=$(( 20 - completed ))
# Erzeuge den Fortschrittsbalken
bar="["
for ((i=0; i<$completed; i++)); do
bar+="#"
done
for ((i=0; i<$remaining; i++)); do
bar+=" "
done
bar+="] $percent%"
# Zeige den Fortschrittsbalken an
echo -ne "$bar\r"
sleep 1
if [ "$progress" -lt "$total" ]; then
tput el
fi
}
total_steps=11
current_step=0
# Python installieren
echo "Installing Python..."
sudo apt-get install -y python3.6
((current_step++))
progress_bar $current_step
# pip installieren
echo "Installing pip..."
sudo apt install -y python3-pip
((current_step++))
progress_bar $current_step
# Virtuelle Umgebung einrichten
echo "Setting up virtual environment..."
python3 -m venv venv
source venv/bin/activate
((current_step++))
progress_bar $current_step
# Flask installieren
echo "Installing Flask..."
pip install flask
((current_step++))
progress_bar $current_step
# API-Flask installieren
echo "Installing API-Flask..."
pip install apiflask
pip install flask-cors
((current_step++))
progress_bar $current_step
# Swagger UI installieren
pip install flask-swagger-ui
((current_step++))
progress_bar $current_step
# LibreOffice installieren
echo "Installing LibreOffice..."
sudo apt-get install -y libreoffice
((current_step++))
progress_bar $current_step
# PyOO installieren
echo "Installing pyoo..."
pip install pyoo
((current_step++))
progress_bar $current_step
# gunicorn installieren
echo "Installing gunicorn..."
pip install gunicorn
((current_step++))
progress_bar $current_step
# jp installieren
echo "Installing jp..."
sudo apt-get install jq
((current_step++))
progress_bar $current_step
# Ordner erstellen
echo "Creating folders..."
mkdir -p ./convert ./uploads
((current_step++))
progress_bar $current_step
chmod +x create-alias.sh
bash create-alias.sh
sleep 5
source ~/.bashrc
((current_step++))
progress_bar $current_step
# Fertigstellung anzeigen
echo -e "\nConvert-Commander installation completed successfully!"

120
start.sh Normal file
View File

@@ -0,0 +1,120 @@
#!/bin/bash
# Virtuelle Umgebung aktivieren
source "$(dirname "$0")/venv/bin/activate"
# Pfad zu den Python-Skripten
PYTHON_SCRIPT_WEB="index:app" # Format: "dateiname:app_name"
PYTHON_SCRIPT_API="api:app" # Format: "dateiname:app_name"
# PID-Dateien für die Gunicorn-Prozesse
PID_FILE_WEB="/tmp/gunicorn_web.pid"
PID_FILE_API="/tmp/gunicorn_api.pid"
# Gunicorn-Befehl zum Starten des Prozesses, inklusive Ports und Fehlerprotokollierung
GUNICORN_CMD_WEB="gunicorn --bind 0.0.0.0:9595 --daemon --pid $PID_FILE_WEB --error-logfile /tmp/gunicorn_web_error.log"
GUNICORN_CMD_API="gunicorn --bind 0.0.0.0:9596 --daemon --pid $PID_FILE_API --error-logfile /tmp/gunicorn_api_error.log"
# Funktion zum Überprüfen, ob ein Prozess läuft
check_status() {
local PID_FILE=$1
local SERVICE_NAME=$2
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null; then
echo "$SERVICE_NAME läuft (PID: $PID)"
else
echo "$SERVICE_NAME läuft nicht, PID-Datei gefunden, aber Prozess nicht aktiv"
rm "$PID_FILE"
fi
else
echo "$SERVICE_NAME läuft nicht"
fi
}
# Starten des Gunicorn-Prozesses
start_service() {
local SCRIPT_NAME=$1
local PID_FILE=$2
local GUNICORN_CMD=$3
if [ -f "$PID_FILE" ] && ps -p "$(cat $PID_FILE)" > /dev/null; then
echo "$SCRIPT_NAME läuft bereits"
else
$GUNICORN_CMD "$SCRIPT_NAME" &
echo "$SCRIPT_NAME wurde gestartet"
fi
}
# Stoppen des Gunicorn-Prozesses
stop_service() {
local PID_FILE=$1
local SERVICE_NAME=$2
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
kill "$PID" 2>/dev/null
rm "$PID_FILE"
echo "$SERVICE_NAME wurde gestoppt"
else
echo "$SERVICE_NAME läuft nicht"
fi
}
# Funktion zum Ausführen eines Bash-Skripts
run_bash_script() {
local SCRIPT_NAME=$1
if [ -x "$SCRIPT_NAME" ]; then
./"$SCRIPT_NAME"
else
echo "Das Skript '$SCRIPT_NAME' ist nicht ausführbar oder nicht gefunden."
fi
}
# Hauptlogik zur Auswahl des Dienstes und der Aktion
case "$1" in
web)
case "$2" in
start)
start_service "$PYTHON_SCRIPT_WEB" "$PID_FILE_WEB" "$GUNICORN_CMD_WEB"
;;
stop)
stop_service "$PID_FILE_WEB" "Web-Dienst"
;;
status)
check_status "$PID_FILE_WEB" "Web-Dienst"
;;
*)
echo "Verwendung: $0 {web|api} {start|stop|status}"
exit 1
;;
esac
;;
api)
case "$2" in
start)
start_service "$PYTHON_SCRIPT_API" "$PID_FILE_API" "$GUNICORN_CMD_API"
;;
stop)
stop_service "$PID_FILE_API" "API-Dienst"
;;
status)
check_status "$PID_FILE_API" "API-Dienst"
;;
token)
run_bash_script "tokenapi.sh"
;;
*)
echo "Verwendung: $0 {web|api} {start|stop|status|token}"
exit 1
;;
esac
;;
*)
echo "Verwendung: $0 {web|api} {start|stop|status|token}"
exit 1
;;
esac
exit 0

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

146
static/script.js Normal file
View File

@@ -0,0 +1,146 @@
var elementsDown2 = document.getElementsByClassName("dropdown-content2");
var value = "";
var valuename = "";
var textGuppe = [".docx", ".txt", ".odt", ".html", ".htm", ".doc", ".epub"];
var tabelleGruppe = [".xls", ".xlsx", ".ods"];
var persentGruppe = [".ppt", ".pptx", ".odp"];
function filterFunction(dropdownClass) {
const input = document.querySelector(`.${dropdownClass} .suche`);
const filter = input.value.toLowerCase();
const pElements = document.querySelectorAll(`.${dropdownClass} p`);
// Alle p-Elemente zuerst wieder sichtbar machen
pElements.forEach(function (item) {
item.style.display = "flex";
});
// Dann nur die Elemente filtern, die der Suchabfrage entsprechen
pElements.forEach(function (item) {
const textValue = item.textContent.toLowerCase(); // Verwenden nur von textContent für bessere Kompatibilität
if (textValue.indexOf(filter) == -1) {
item.style.display = "none";
} else {
item.style.display = "flex";
}
});
// Führe 'meineFunktion' nur aus, wenn das Suchfeld leer ist
if (elementsDown2.length > 0 && window.getComputedStyle(elementsDown2[0]).display === "flex" && input.value.trim() === "") {
meineFunktion();
}
}
function setFileFunction(name, filename) {
value = name;
valuename = filename;
console.log(value, valuename);
if (
elementsDown2.length > 0 &&
window.getComputedStyle(elementsDown2[0]).display === "flex"
) {
var dropbtn2 = document.getElementsByClassName("dropbtn2");
if (dropbtn2.length > 0) {
dropbtn2[0].innerHTML = valuename;
}
}
return [value, valuename];
}
document.getElementById('fileInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const fileName = file.name; // Holt den Dateinamen
const fileExtension = '.' + fileName.split('.').pop(); // Holt die Dateiendung mit Punkt
console.log(`Dateiendung: ${fileExtension}`);
meineFunktion(fileExtension); // Funktion zum Steuern der Anzeige aufrufen
} else {
console.log('Keine Datei ausgewählt.');
}
});
function meineFunktion(name) {
var elementsText = document.getElementsByClassName("text");
var elementsExel = document.getElementsByClassName("exel");
var elementsPPT = document.getElementsByClassName("ppt");
if (textGuppe.includes(name)) {
for (var i = 0; i < elementsExel.length; i++) {
elementsExel[i].style.display = "none";
}
for (var i = 0; i < elementsPPT.length; i++) {
elementsPPT[i].style.display = "none";
}
} else if (tabelleGruppe.includes(name)) {
for (var i = 0; i < elementsPPT.length; i++) {
elementsPPT[i].style.display = "none";
}
for (var i = 0; i < elementsText.length; i++) {
elementsText[i].style.display = "none";
}
} else if (persentGruppe.includes(name)) {
for (var i = 0; i < elementsText.length; i++) {
elementsText[i].style.display = "none";
}
for (var i = 0; i < elementsExel.length; i++) {
elementsExel[i].style.display = "none";
}
}
}
function sendData() {
fetch('/empfange_daten', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ daten: valuename })
})
.then(response => {
if (!response.ok) {
throw new Error('Netzwerkantwort war nicht ok');
}
return response.json();
})
.then(data => {
console.log('Erfolg:', data);
// Hier können Sie das Formular manuell absenden
document.querySelector('form').submit();
})
.catch((error) => {
console.error('Fehler:', error);
// Hier können Sie dem Benutzer eine Fehlermeldung anzeigen
});
}
document.querySelectorAll('.dropdown2').forEach(dropdown => {
dropdown.addEventListener('mouseenter', () => {
const dropdownContent = dropdown.querySelector('.dropdown-content2');
dropdownContent.style.maxHeight = '300px';
dropdownContent.style.opacity = '1';
// Breite wird jetzt durch CSS-Transition geregelt
});
dropdown.addEventListener('mouseleave', () => {
const dropdownContent = dropdown.querySelector('.dropdown-content2');
dropdownContent.style.maxHeight = '0';
dropdownContent.style.opacity = '0';
// Breite wird jetzt durch CSS-Transition geregelt
});
});
function updateFileName() {
const fileInput = document.getElementById('fileInput');
const fileLabel = document.getElementById('fileLabel');
if (fileInput.files.length > 0) {
fileLabel.textContent = fileInput.files[0].name;
} else {
fileLabel.textContent = 'Datei auswählen';
}
}

265
static/style.css Normal file
View File

@@ -0,0 +1,265 @@
body {
background-color: #1B1B1B;
color: white;
font-family: Arial, sans-serif;
width: 80%;
margin: 0 auto;
}
.main {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
h1 {
margin-bottom: 30px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo,
.link {
padding: 10px;
margin: 5px;
font-size: 20px;
}
.logo {
text-align: left;
color: #94EBEB;
}
.link {
text-align: right;
color: white;
}
.link a {
text-decoration: none;
color: white;
}
.link a:hover {
color: #a0a0a0;
transition: all 0.3s;
}
.button-container {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 20px;
}
.button-wrapper {
display: flex;
flex-direction: column;
width: max-content;
}
.buttons {
display: inline-block;
padding: 12px 24px;
background-color: #94EBEB;
border: none;
border-radius: 10px;
color: #1B1B1B;
cursor: pointer;
font-size: 16px;
min-width: 150px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.3s ease;
}
.buttons:hover {
background-color: #7CD1D1;
transform: translateY(0px);
box-shadow: 0 4px 8px rgba(148, 235, 235, 0.2);
}
.dropdown2 {
position: relative;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
width: 120%;
}
.dropdown-content2 {
padding: 10px;
position: absolute;
top: 115%;
left: 0;
background-color: #2A2A2A;
border-radius: 10px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.3);
z-index: 1;
width: 90%;
max-height: 400px;
overflow-y: auto;
opacity: 0;
transition: opacity 0.3s ease-out, top 0.2s ease-out;
scrollbar-width: thin;
scrollbar-color: #94EBEB #2A2A2A;
}
.dropdown2:hover .dropdown-content2 {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
top: 100%;
opacity: 1;
padding: 10px;
}
.dropbtn2 {
background-color: #b0f1f1;
}
.dropdown2:hover .dropbtn2 {
background-color: #7CD1D1;
transform: translateY(0px);
box-shadow: 0 4px 8px rgba(148, 235, 235, 0.2);
}
.dropdown-content2::-webkit-scrollbar {
width: 8px;
}
.dropdown-content2::-webkit-scrollbar-track {
background: #2A2A2A;
border-radius: 10px;
}
.dropdown-content2::-webkit-scrollbar-thumb {
background-color: #94EBEB;
border-radius: 4px;
}
.dropdown-content2 p {
color: #FFFFFF;
padding: 8px 12px;
margin: 4px;
text-decoration: none;
background-color: #3A3A3A;
border-radius: 5px;
flex: 1 0 calc(33.33% - 8px);
text-align: center;
box-sizing: border-box;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.dropdown-content2 p:hover {
background-color: #4A4A4A;
transform: scale(1.05);
}
#file {
border-radius: 10px;
transition: all 0.3s;
}
#fileInput {
display: none;
}
.suche {
width: 100%;
padding: 10px;
box-sizing: border-box;
margin-bottom: 8px;
background-color: #2A2A2A;
border: 1px solid #000000;
color: #FFFFFF;
border-radius: 5px;
}
.suche::placeholder {
color: #888888;
}
@media (min-width: 600px){
.buttons:hover {
transform: translateY(-2px);
}
.dropdown2:hover .dropbtn2 {
transform: translateY(-2px);
}
}
@media (max-width: 600px) {
body{
width: 95%;
margin: 0 auto;
}
.main {
padding: 20px 10px;
}
.button-container {
flex-direction: column;
align-items: center;
gap: 10px;
width: 100%;
}
.button-wrapper {
width: 95%;
}
.buttons {
width: 95%;
min-width: unset;
font-size: 16px;
padding: 15px 10px;
}
.dropdown-content2 {
width: 95%;
top: 100%;
}
.dropdown-content2 p {
flex: 1 0 calc(50% - 8px);
margin: 4px;
}
#submitButton {
width: 100%;
}
h1 {
font-size: 24px;
text-align: center;
}
.dropdown2:hover .dropdown-content2 {
overflow-y: auto;
}
}
@media (max-width: 415px) {
#submitButton {
width: 101%;
}
.dropdown-content2 p {
flex: 1 0 100%;
}
}
@media (max-width: 364px) {
#submitButton {
width: 102%;
}
}

75
static/swagger.json Normal file
View File

@@ -0,0 +1,75 @@
{
"swagger": "2.0",
"info": {
"title": "Convert-Commander API",
"version": "1.0"
},
"paths": {
"/upload": {
"post": {
"tags": ["File Conversion"],
"summary": "Upload a file and convert it to the specified format",
"parameters": [
{
"name": "X-API-Token",
"in": "header",
"required": true,
"type": "string",
"description": "API token for authentication"
},
{
"name": "file",
"in": "formData",
"required": true,
"type": "file",
"description": "The file to upload"
},
{
"name": "format",
"in": "formData",
"required": true,
"type": "string",
"description": "The target format for conversion"
}
],
"responses": {
"200": {
"description": "Successfully converted file"
},
"400": {
"description": "Invalid request"
},
"401": {
"description": "Unauthorized"
},
"500": {
"description": "Server error"
}
}
}
},
"/clear": {
"post": {
"tags": ["Maintenance"],
"summary": "Clear all files in the upload and convert folders",
"parameters": [
{
"name": "X-API-Token",
"in": "header",
"required": true,
"type": "string",
"description": "API token for authentication"
}
],
"responses": {
"200": {
"description": "Folders successfully cleared"
},
"401": {
"description": "Unauthorized"
}
}
}
}
}
}

190
templates/docs.html Normal file
View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docs</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<div class="header">
<div class="logo">
<h3>Convert-Commander</h3>
</div>
<div class="link">
<a href="/">Convert</a>
</div>
</div>
<br>
<table>
<h5>Which file can you convert?</h5>
<tr>
<td>
<p>.docs/.doc</p>
<p>.txt</p>
<p>.odt</p>
<p>.html/.htm</p>
</td>
<td>
<p>to</p>
</td>
<td>
<p>.docs/.doc</p>
<p>.txt</p>
<p>.odt</p>
<p>.html/.htm</p>
<p>.epub</p>
<p>.pdf</p>
</td>
</tr>
<tr>
<td>
<p>.xlsx/.xls</p>
<p>.ods</p>
</td>
<td>
<p>to</p>
</td>
<td>
<p>.xlsx/.xls</p>
<p>.ods</p>
<p>.pdf</p>
</td>
</tr>
<tr>
<td>
<p>.pptx/.ppt</p>
<p>.odp</p>
</td>
<td>
<p>to</p>
</td>
<td>
<p>.pptx/.ppt</p>
<p>.odp</p>
<p>.pdf</p>
</td>
</tr>
</table>
<div class="exp">
<h5>What are the files?</h5>
<p><b>.docs:</b> This is not a standard file extension, likely a typo for .docx, which is a Microsoft Word document format.</p>
<p><b>.doc:</b> A Microsoft Word document format, commonly used for word processing.</p>
<p><b>.txt:</b> A plain text file that contains unformatted text.</p>
<p><b>.odt:</b> An OpenDocument Text file, typically used by OpenOffice and LibreOffice for word processing.</p>
<p><b>.html:</b> A HyperText Markup Language file, used to structure content on the web.</p>
<p><b>.htm:</b> A variant of the .html extension, also used for web pages.</p>
<p><b>.epub:</b> An electronic publication (ePub) file, commonly used for eBooks and supported by many eReader devices and applications.</p>
<p><b>.xlsx:</b> A Microsoft Excel file format, used for spreadsheets.</p>
<p><b>.xls:</b> An older Microsoft Excel file format, also used for spreadsheets.</p>
<p><b>.pdf:</b> A Portable Document Format, widely used for sharing formatted documents.</p>
<p><b>.ods:</b> An OpenDocument Spreadsheet file, used by OpenOffice and LibreOffice for spreadsheets.</p>
<p><b>.odp:</b> An OpenDocument Presentation file, used by OpenOffice and LibreOffice for presentations.</p>
<p><b>.pptx:</b> A Microsoft PowerPoint presentation format, used for creating slideshows.</p>
<p><b>.ppt:</b> An older Microsoft PowerPoint format, also used for presentations.</p>
</div>
</body>
<style>
body {
background-color: #1B1B1B;
color: white;
font-family: Arial, sans-serif;
width: 80%;
margin: 0 auto;
/* Zentriert den Inhalt */
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo,
.link {
padding: 10px;
margin: 5px;
font-size: 20px;
/* Einheitliche Schriftgröße */
}
.logo {
text-align: left;
color: #94EBEB;
}
.link {
text-align: right;
color: white;
}
.link a {
text-decoration: none;
color: white;
}
.link a:hover {
color: #a0a0a0;
transition: all 0.3s;
}
table {
border-collapse: collapse;
/* Entfernt Lücken zwischen den Zellen */
text-align: center;
width: 100%;
}
h5{
font-size: 25px;
color: #94EBEB;
text-align: center;
}
table td,
table tr {
vertical-align: middle;
text-align: center;
}
tr {
border-bottom: 1px solid #94EBEB;
/* Fügt eine weiße Linie unter jeder Zelle hinzu */
}
tr p {
font-size: 25px;
}
.exp {
margin-top: 50px;
margin-left: 11%;
margin-right: 11%;
}
.exp p {
font-size: 20px;
}
@media (max-width: 600px) {
body{
width: 95%;
margin: 0 auto;
}
.exp {
margin-left: 5%;
margin-left: 5%;
}
}
</style>
</html>

62
templates/index.html Normal file
View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dropdown Menu</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="header">
<div class="logo">
<h3>Convert-Commander</h3>
</div>
<div class="link">
<a href="/docs">docs</a>
</div>
</div>
<div class="main">
<h1>Convert-Commander</h1>
<div class="button-container">
<div class="button-wrapper">
<form action="/" method="post" enctype="multipart/form-data">
<label class="buttons" for="fileInput" id="fileLabel">Select File</label>
<input type="file" name="file" id="fileInput" required onchange="updateFileName()">
</div>
<div class="button-wrapper dropdown2" onmouseover="meineFunktion()">
<div class="buttons dropbtn2" onclick="event.preventDefault(); meineFunktion()" id="file">File Type</div>
<div class="dropdown-content2">
<input class="suche" type="text" onkeyup="filterFunction('dropdown-content2')" placeholder="Search...">
<p class="text" onclick="setFileFunction('text', 'txt')">txt</p>
<p class="text" onclick="setFileFunction('text', 'odt')">odt</p>
<p class="text" onclick="setFileFunction('text', 'html')">html</p>
<p class="text" onclick="setFileFunction('text', 'htm')">htm</p>
<p class="text" onclick="setFileFunction('text', 'doc')">doc</p>
<p class="text" onclick="setFileFunction('text', 'docx')">docx</p>
<p class="text" onclick="setFileFunction('text', 'epub')">epub</p>
<p class="none" onclick="setFileFunction('none', 'pdf')">pdf</p>
<p class="ppt" onclick="setFileFunction('ppt', 'pptx')">pptx</p>
<p class="ppt" onclick="setFileFunction('ppt', 'ppt')">ppt</p>
<p class="ppt" onclick="setFileFunction('ppt', 'odp')">odp</p>
<p class="exel" onclick="setFileFunction('exel', 'xls')">xls</p>
<p class="exel" onclick="setFileFunction('exel', 'xlsx')">xlsx</p>
<p class="exel" onclick="setFileFunction('exel', 'ods')">ods</p>
</div>
</div>
<div class="button-wrapper">
<input class="buttons" type="button" value="Upload" id="submitButton" onclick="sendData()">
</div>
</div>
</div>
<p id="upload-status">
{% if status %}
{{ status }}
{% endif %}
</p>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

25
tokenapi.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# URL of your Flask application
API_URL="http://localhost:5001"
# Make a POST request to the /generate_token endpoint
response=$(curl -s -X POST "${API_URL}/generate_token")
# Check if the request was successful
if [[ $? -ne 0 ]]; then
echo "Error: Unable to connect to the API."
exit 1
fi
# Extract the token from the JSON response using jq
token=$(echo "$response" | jq -r '.token')
# Check if the token was extracted successfully
if [[ $token == "null" ]]; then
echo "Error: Token not found in the response."
exit 1
fi
# Output the token
echo "Token: $token"