From 8fa433f543192332d06681e5d065bfc596f606c8 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 30 Jan 2024 11:54:51 +0100 Subject: [PATCH] containerized --- .dockerignore | 3 + .gitignore | 5 + .woodpecker.yml | 32 ++++++ Dockerfile | 15 +++ deployment/decrypt-secrets.sh | 43 ++++++++ deployment/deploy-yml.tmpl | 62 ++++++++++++ deployment/deploy.sh | 43 ++++++++ deployment/encrypt-secrets.sh | 27 +++++ deployment/oidc-config.json | 13 +++ src/Run.py | 160 ++++++++++++++++++++++++++++++ src/db.py | 36 +++++++ src/nu.csv | 86 ++++++++++++++++ src/nutrition.db | Bin 0 -> 16384 bytes src/requirements.txt | 20 ++++ src/static/style.css | 123 +++++++++++++++++++++++ src/templates/index.html | 182 ++++++++++++++++++++++++++++++++++ src/templates/nutrition.html | 78 +++++++++++++++ start.sh | 4 + 18 files changed, 932 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .woodpecker.yml create mode 100644 Dockerfile create mode 100755 deployment/decrypt-secrets.sh create mode 100644 deployment/deploy-yml.tmpl create mode 100755 deployment/deploy.sh create mode 100755 deployment/encrypt-secrets.sh create mode 100644 deployment/oidc-config.json create mode 100644 src/Run.py create mode 100644 src/db.py create mode 100644 src/nu.csv create mode 100644 src/nutrition.db create mode 100644 src/requirements.txt create mode 100644 src/static/style.css create mode 100644 src/templates/index.html create mode 100644 src/templates/nutrition.html create mode 100755 start.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3add265 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +src/__pycache__ +src/ENV +src/.venv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe97e28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +deployment/secrets.txt +src/.venv +src/__pycache__ +src/ENV + diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..442ec46 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,32 @@ +steps: + build: + image: plugins/kaniko + settings: + repo: gitea.hottis.de/wn/oidc-python-example + registry: + from_secret: container_registry + tags: latest,${CI_COMMIT_SHA},${CI_COMMIT_TAG} + username: + from_secret: container_registry_username + password: + from_secret: container_registry_password + dockerfile: Dockerfile + when: + - event: [push, tag] + + deploy: + image: portainer/kubectl-shell:latest + secrets: + - source: kube_config + target: KUBE_CONFIG_CONTENT + - source: encryption_key + target: ENCRYPTION_KEY + - source: secrets_checksum + target: MD5_CHECKSUM + commands: + - export IMAGE_TAG=$CI_COMMIT_TAG + - printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig + - export KUBECONFIG=/tmp/kubeconfig + - ./deployment/deploy.sh + when: + - event: tag diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c60102b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-alpine3.19 + +ARG APP_DIR="/opt/app" + +COPY ./src/ ${APP_DIR}/ +COPY start.sh ${APP_DIR}/ + +WORKDIR ${APP_DIR} + +RUN pip install -r requirements.txt + +EXPOSE 8080 + +CMD "./start.sh" + diff --git a/deployment/decrypt-secrets.sh b/deployment/decrypt-secrets.sh new file mode 100755 index 0000000..d971ca7 --- /dev/null +++ b/deployment/decrypt-secrets.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +if [ "$ENCRYPTION_KEY" = "" ]; then + echo "ENCRYPTION_KEY not set" + exit 1 +fi + +if [ "$MD5_CHECKSUM" = "" ]; then + echo "No checksum given" + exit 1 +fi + +SECRETS_CIPHERTEXT_FILE=secrets.enc +SECRETS_PLAINTEXT_FILE=/tmp/secrets +TMP_FILE=`mktemp` +POD_NAME_SUFFIX=`date +%s` + +cat $SECRETS_CIPHERTEXT_FILE | \ + kubectl run openssl-$POD_NAME_SUFFIX \ + --rm \ + --image bitnami/debian-base-buildpack:latest \ + --env KEY=$ENCRYPTION_KEY \ + -i \ + -q \ + -- \ + /bin/sh -c "openssl enc -aes-256-cbc -salt -pass env:KEY -a -d" > \ + $TMP_FILE + +if [ `uname` = "Darwin" ]; then + CALCULATED_CHECKSUM=`cat $TMP_FILE | md5` +elif [ `uname` = "Linux" ]; then + CALCULATED_CHECKSUM=`cat $TMP_FILE | md5sum - | awk '{print $1}'` +fi + +if [ "$MD5_CHECKSUM" != "$CALCULATED_CHECKSUM" ]; then + echo "Invalid checksum" + exit 1 +fi + +# cat $TMP_FILE +mv $TMP_FILE $SECRETS_PLAINTEXT_FILE + + diff --git a/deployment/deploy-yml.tmpl b/deployment/deploy-yml.tmpl new file mode 100644 index 0000000..ada1c1f --- /dev/null +++ b/deployment/deploy-yml.tmpl @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: oidc-python-example + labels: + app: oidc-python-example +spec: + replicas: 1 + selector: + matchLabels: + app: oidc-python-example + template: + metadata: + labels: + app: oidc-python-example + spec: + containers: + - name: oidc-python-example + image: %IMAGE% + envFrom: + - secretRef: + name: secrets + ports: + - containerPort: 8080 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: oidc-python-example +spec: + type: ClusterIP + selector: + app: oidc-python-example + ports: + - name: http + targetPort: 8080 + port: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: oidc-python-example + annotations: + cert-manager.io/cluster-issuer: letsencrypt-production-http +spec: + tls: + - hosts: + - oidc-python-example.hottis.de + secretName: oidc-python-example-cert + rules: + - host: oidc-python-example.hottis.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: oidc-python-example + port: + number: 80 + diff --git a/deployment/deploy.sh b/deployment/deploy.sh new file mode 100755 index 0000000..8bff33d --- /dev/null +++ b/deployment/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +if [ "$IMAGE_TAG" == "" ]; then + echo "Make sure IMAGE_TAG is set" + exit 1 +fi + + +IMAGE_NAME=gitea.hottis.de/wn/oidc-python-example +NAMESPACE=oidc-python-example +DEPLOYMENT_DIR=$PWD/deployment + +pushd $DEPLOYMENT_DIR > /dev/null +./decrypt-secrets.sh || exit 1 +. /tmp/secrets +rm /tmp/secrets + +CLIENT_SECRETS=`cat oidc-config.json | sed -e's!%CLIENT_SECRET%!'$CLIENT_SECRET'!'` + +kubectl create namespace $NAMESPACE \ + --dry-run=client \ + -o yaml | \ + kubectl -f - apply + +kubectl create secret generic secrets \ + --dry-run=client \ + -o yaml \ + --save-config \ + --from-literal=SECRET="$SECRET" \ + --from-literal=CLIENT_SECRETS="$CLIENT_SECRETS" \ + --from-literal=PGUSER="$PGUSER" \ + --from-literal=PGPASSWORD="$PGPASSWORD" \ + --from-literal=PGDATABASE="$PGDATABASE" \ + --from-literal=PGHOST="timescaledb.database.svc.cluster.local" \ + --from-literal=PGSSLMODE="require" | \ + kubectl apply -f - -n $NAMESPACE + +cat $DEPLOYMENT_DIR/deploy-yml.tmpl | \ + sed -e 's,%IMAGE%,'$IMAGE_NAME':'$IMAGE_TAG','g | \ + kubectl apply -f - -n $NAMESPACE + +popd > /dev/null + diff --git a/deployment/encrypt-secrets.sh b/deployment/encrypt-secrets.sh new file mode 100755 index 0000000..38a7b6d --- /dev/null +++ b/deployment/encrypt-secrets.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +ENCRYPTION_KEY=`openssl rand -hex 32` +echo $ENCRYPTION_KEY + +SECRETS_PLAINTEXT_FILE=secrets.txt +SECRETS_CIPHERTEXT_FILE=secrets.enc + +if [ `uname` = "Darwin" ]; then + cat $SECRETS_PLAINTEXT_FILE | md5 +elif [ `uname` = "Linux" ]; then + cat $SECRETS_PLAINTEXT_FILE | md5sum - | awk '{print $1}' +fi + +POD_NAME_SUFFIX=`date +%s` + +cat $SECRETS_PLAINTEXT_FILE | \ + kubectl run openssl-$POD_NAME_SUFFIX \ + --rm \ + --image bitnami/debian-base-buildpack:latest \ + --env KEY=$ENCRYPTION_KEY \ + -i \ + -q \ + -- \ + /bin/sh -c "openssl enc -aes-256-cbc -salt -pass env:KEY -a" > \ + $SECRETS_CIPHERTEXT_FILE + diff --git a/deployment/oidc-config.json b/deployment/oidc-config.json new file mode 100644 index 0000000..b587893 --- /dev/null +++ b/deployment/oidc-config.json @@ -0,0 +1,13 @@ +{ + "web": { + "issuer": "https://auth2.hottis.de/realms/hottis", + "auth_uri": "https://auth2.hottis.de/ealms/hottis/protocol/openid-connect/auth", + "client_id": "oidc-python-example", + "client_secret": "%CLIENT_SECRET%", + "redirect_uris": [ + "https://oidc-python-example.hottis.de/*" + ], + "userinfo_uri": "https://auth2.hottis.de/realms/hottis/protocol/openid-connect/userinfo", + "token_uri": "https://auth2.hottis.de/realms/hottis/protocol/openid-connect/token" + } +} diff --git a/src/Run.py b/src/Run.py new file mode 100644 index 0000000..9d3546a --- /dev/null +++ b/src/Run.py @@ -0,0 +1,160 @@ +from flask import Flask, request, render_template, jsonify, redirect, url_for +import sqlite3 + +app = Flask(__name__) +app.config.update({ + 'SECRET_KEY': "fdsgffdgretfsdgfsf" +}) + +# Datenbankverbindung konfigurieren +def get_db_connection(): + conn = sqlite3.connect('nutrition.db') # 'nutrition.db' ist der Name der Datenbankdatei + conn.row_factory = sqlite3.Row # Ermöglicht den Zugriff auf Daten durch Spaltennamen + return conn + + +def init_db(): + conn = get_db_connection() + cursor = conn.cursor() + + # Erstellen der Tabelle + cursor.execute(''' + CREATE TABLE IF NOT EXISTS nutrition_table ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + kcal REAL, + EW REAL, + Fett REAL, + KH REAL, + BST REAL, + CA REAL + ) + ''') + + # Testdaten einfügen + test_data = [ + ('Apfel', 52, 0.3, 0.2, 14, 0.2, 6), + ('Banane', 89, 1.1, 0.3, 23, 0.3, 5), + ('Karotte', 41, 0.9, 0.2, 10, 0.2, 3), + ('Tomate', 18, 0.9, 0.2, 3.9, 0.2, 4), + ('Brokkoli', 34, 2.8, 0.4, 6.6, 0.4, 2), + ('Spinat', 23, 2.9, 0.4, 3.6, 0.4, 99), + ('Kartoffel', 77, 2, 0.1, 17, 0.1, 12), + ('Huhn', 239, 27, 14, 0, 0, 2), + ('Lachs', 208, 20, 13, 0, 0, 1), + ('Ei', 155, 13, 11, 1.1, 1, 1) + ] + + cursor.executemany('INSERT INTO nutrition_table (name, kcal, EW, Fett, KH, BST, CA) VALUES (?, ?, ?, ?, ?, ?, ?)', test_data) + + conn.commit() + conn.close() + + + +def calculate_nutrition(food, weight): + conn = get_db_connection() + cursor = conn.cursor() + + # Abfrage der Nährwertdaten aus der Datenbank + cursor.execute('SELECT kcal, EW, Fett, KH, BST, CA FROM nutrition_table WHERE name = ?', (food,)) + + result = cursor.fetchone() + conn.close() + + if result: + # Runden und Berechnen der Nährwerte basierend auf dem Gewicht + kcal, ew, fett, kh, bst, ca = result + nutrition_values = [ + round(kcal * weight / 100), # kcal gerundet auf ganze Zahl + round(ew * weight / 100, 1), # EW gerundet auf eine Dezimalstelle + round(fett * weight / 100, 1), # Fett gerundet auf eine Dezimalstelle + round(kh * weight / 100, 1), # KH gerundet auf eine Dezimalstelle + round(bst * weight / 100, 1), # BST gerundet auf eine Dezimalstelle + round(ca * weight / 100) # CA gerundet auf ganze Zahl + ] + return nutrition_values + else: + return None + + +# Index-Route +@app.route('/') +def index(): + return render_template('index.html') + +# ... + +@app.route('/get_products') +def get_products(): + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('SELECT name FROM nutrition_table') + products = cursor.fetchall() + conn.close() + print("ter") + return {'products': [product[0] for product in products]} + +# ... + + + + +# Route zum Hinzufügen und Berechnen von Lebensmitteln +@app.route('/add_lm', methods=['GET']) +def add_lm(): + food = request.args.get('food') + weight = float(request.args.get('weight')) + + nutrition = calculate_nutrition(food, weight) + if nutrition: + # Extrahieren der einzelnen Nährwerte + kcal, ew, fett, kh, bst, ca = nutrition + return jsonify({ + "food": food, + "kcal": kcal, + "ew": ew, + "fett": fett, + "kh": kh, + "bst": bst, + "ca": ca + }) + else: + return "Lebensmittel nicht gefunden.", 404 + + + +@app.route('/add_nutrition', methods=['POST']) +def add_nutrition(): + food = request.form.get('food') + kcal = float(request.form.get('kcal')) + ew = float(request.form.get('ew')) + fett = float(request.form.get('fett')) + kh = float(request.form.get('kh')) + bst = float(request.form.get('bst')) + ca = float(request.form.get('ca')) + + print("test") + # Verbindung zur Datenbank herstellen und Daten einfügen + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("INSERT INTO nutrition_table (name, kcal, ew, fett, kh, bst, ca) VALUES (?, ?, ?, ?, ?, ?, ?)", + (food, kcal, ew, fett, kh, bst, ca)) + conn.commit() + conn.close() + + return redirect(url_for('nutrition')) + + +@app.route('/nutrition') +def nutrition(): + return render_template('nutrition.html') + + + + +if __name__ == '__main__': + #init_db() + app.run(debug=True) + + diff --git a/src/db.py b/src/db.py new file mode 100644 index 0000000..b52c315 --- /dev/null +++ b/src/db.py @@ -0,0 +1,36 @@ +import csv +import sqlite3 + +# Pfad zur Ihrer CSV-Datei +csv_file_path = 'nu.csv' + +# Pfad zur Ihrer SQLite-Datenbank +sqlite_db_path = 'nutrition.db' + +# Verbindung zur SQLite-Datenbank herstellen +conn = sqlite3.connect(sqlite_db_path) +cursor = conn.cursor() + +# Erstellen der Tabelle (falls noch nicht vorhanden) +cursor.execute(''' + CREATE TABLE IF NOT EXISTS nutrition_table ( + name TEXT, + kcal REAL, + EW REAL, + Fett REAL, + KH REAL, + BST REAL, + Ca REAL + ) +''') + +# Öffnen der CSV-Datei und Einfügen der Daten in die Datenbank +with open(csv_file_path, newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + next(reader, None) # Überspringen der Kopfzeile + for row in reader: + cursor.execute('INSERT INTO nutrition_table (name, kcal, EW, Fett, KH, BST, Ca) VALUES (?, ?, ?, ?, ?, ?, ?)', row) + +# Änderungen speichern und Verbindung schließen +conn.commit() +conn.close() diff --git a/src/nu.csv b/src/nu.csv new file mode 100644 index 0000000..69a2e3d --- /dev/null +++ b/src/nu.csv @@ -0,0 +1,86 @@ +name,kcal,EW,Fett,KH,BST,Ca +Zitronensaft,38,0.4,0.5,3.8,0.1,11 +Zucker,405,0.0,0.0,100.0,0.0,2 +Trinkmilch3.5,65,3.4,3.6,4.7,0.0,120 +Hühnerei50,137,11.9,9.3,1.5,0.0,51 +Pflanzenmargarine,722,0.2,80.0,0.4,0.0,8 +Sahne30,309,2.4,31.7,3.4,0.0,80 +Maisstärke,353,0.4,0.1,85.9,1.0,0 +Paniermehl,368,10.1,2.1,73.5,5.3,50 +Weizengrieß,335,9.6,0.8,69.0,7.1,17 +Mehl405,343,9.8,1.0,71.8,4.0,15 +Rapsöl,884,0,100.0,0,0,0 +Kartoffeln,76,1.9,0.1,15.6,1.2,6 +Butter,754,0.7,83.2,0.7,0,13 +Zwiebeln,30,1.2,0.3,4.9,1.4,31 +Kartoffelstärke,341,0.6,0.1,83.1,0.1,35 +Olivenöl,884,0,99.8,0.2,0,0 +Möhre,39,0.8,0.2,6.8,3.6,21 +Rote linsen,350,23.9,2.2,52.3,10.8,48 +Gemüsebruhe verz,7,1.6,0,1,0,12 +Rindfleisch keule,148,20.0,7.6,0,0,6 +Rinderbouillon,4,0.2,0,1,0,5 +Meerettich iD,78,2.8,0.3,11.7,7.5,105 +Saure sahne10,117,3.1,10.0,3.7,0,110 +Joghurt3.5, 64,3.3,3.5,4.4,0,120 +Dill,65,3.7,0.8,8.0,5.3,230 +Schnittlauch,40,3.6,0.7,1.6,6.0,129 +Gartenkresse,41,4.2,0.7,2.5,3.5,214 +Petersilie,60,4.4,0.4,7.4,4.3,179 +Mandelstifte,610,18.7,54.1,5.4,13.5,252 +Porree,29,2.2,0.3,3.3,2.3,63 +Champignons,20,2.7,0.3,0.6,2.0,10 +Sellerie,27,1.6,0.3,2.3,4.2,50 +Apfelsine,47,1.0,0.2,8.3,2.2,42 +Birne,58,0.5,0.3,12.4,2.8,9 +Apfel,57,0.3,0.6,11.4,2.0,7 +Kiwi grün,55,0.9,0.6,9.1,3.0,28 +Banane,93,1.2,0.2,20.0,1.8,8 +Schweinefleisch bug,217,17.0,16.5,0.0,0.0,9 +Gouda48,370,22.7,29.9,0,0,811 +Blumenkohl,28,2.5,0.3,2.3,2.9,22 +Knoblauch,145,6.1,0.1,28.4,1.8,38 +Senf,88,6.0,4.0,6.0,1.0,124 +Blattspinat roh,21,2.7,0.3,0.6,2.6,117 +Buttermilch,37,3.5,0.5,4.0,0.0,109 +Himbeere,37,1.3,0.3,4.8,4.7,40 +Salz dill gurken,9,0.4,0.1,1.3,0.5,18 +Schmand20,205,2.8,20.0,3.6,0.0,100 +Aspikpulver,338,84.2,0.1,0.0,0.0,11 +Lachs atlantischer,210,20.4,13.4,0.3,0.0,4 +Pinienkerne,589,24.0,50.7,7.3,7.2,26 +Zwieback,385,9.9,4.3,73.1,5.2,42 +Speisequark,72,13.5,0.3,3.2,0.0,92 +Basilikum,47,3.1,0.8,5.1,3.1,369 +Mayonaise50,490,0.5,52.0,5.0,0.0,10 +Weizenbrötchen,292,10.2,1.8,55.9,3.6,49 +Seelachs köhler,81,18.3,0.9,0,0,14 +Bohnen grün,21,1.7,0.1,2.0,2.3,34 +Aprikosen dose,70,0.5,0.1,15.1,1.4,11 +Orangenfilets,47,1.0,0.2,8.3,2.2,42 +Mirabellen dose,66,0.7,0.2,15.0,0.9,12 +Pfirsich dose,67,0.4,0.1,15.5,1.1,4 +Vanillezucker,405,0.0,0.0,100.0,0,2 +Vanille pp,346,0.5,0.0,86.0,1,15 +Brokkoli,34,3.8,0.2,2.7,3.0,58 +Cornichons,15,1,0.1,2.0,0.8,0 +Kopfsalat,14,1.2,0.2,1.1,1.4,20 +Rosenkohl,43,4.5,0.3,3.3,4.4,31 +Rotkohl,27,1.5,0.2,3.5,2.5,35 +Spinat,19,2.3,0.3,0.5,2.3,120 +Tomate,18,1.0,0.2,2.6,1.0,9 +Tomatenmark,177,9.7,2.0,25.1,1.6,106 +Wirsing,30,3.0,0.4,2.9,2.5,64 +Zucchini,23,2.0,0.3,2.3,1.1,25 +Mais Dose,81,3.2,1.2,12.6,2.8,8 +Pfefferminze,50,3.8,0.7,5.3,3.0,179 +Erdbeere,36,0.8,0.4,5.5,2.0,24 +Himbeere,37,1.3,0.3,4.8,4.7,40 +Orange/apfelsine,47,1.0,0.2,8.3,2.2,42 +Orangensaft frisch,44,0.7,0.1,8.7,0.4,11 +Weintraube,69,0.7,0.3,15.2,1.5,12 +Naturreis,349,7.2,2.2,74.1,2.2,16 +Mehl405,343,9.8,1.0,71.8,4.0,15 +Mehl550,346,9.8,1.1,72.0,4.3,17 +Milchreis,316,6.4,0.8,80.2,1.1,6 +Kartoffelstärke,341,0.6,0.1,83.1,0.1,35 \ No newline at end of file diff --git a/src/nutrition.db b/src/nutrition.db new file mode 100644 index 0000000000000000000000000000000000000000..2384afcdd8c9754250b153845a386a0ab695476c GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..9ccfda3 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,20 @@ +Authlib==1.3.0 +blinker==1.7.0 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 +click==8.1.7 +cryptography==42.0.1 +Flask==3.0.1 +flask-oidc==2.1.1 +gunicorn==21.2.0 +idna==3.6 +itsdangerous==2.1.2 +Jinja2==3.1.3 +MarkupSafe==2.1.4 +packaging==23.2 +pycparser==2.21 +requests==2.31.0 +typing_extensions==4.9.0 +urllib3==2.1.0 +Werkzeug==3.0.1 diff --git a/src/static/style.css b/src/static/style.css new file mode 100644 index 0000000..3f4a3f8 --- /dev/null +++ b/src/static/style.css @@ -0,0 +1,123 @@ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 20px; +} + +form { + margin-bottom: 20px; +} + +input, button { + padding: 10px; + margin: 5px; + border: 1px solid #ddd; + border-radius: 4px; +} + +button { + cursor: pointer; + background-color: #008C50; /* Helles Blau */ + color: white; + border: none; +} + +button:disabled { + background-color: #cccccc; + color: #666666; + cursor: not-allowed; +} + +button:not(:disabled):hover { + background-color: #007344; /* Dunkleres Blau */ +} + +button#remove-button { + background-color: #f443366f; /* Helles Rot */ +} + +button#remove-button:not(:disabled):hover { + background-color: #d32f2f3d; /* Dunkleres Rot */ +} + +table { + width: 100%; + border-collapse: collapse; +} + +table, th, td { + border: 1px solid #ddd; +} + +th, td { + text-align: left; + padding: 8px; +} + +th { + background-color: #4CAF50; /* Grün */ + color: white; +} + +tr:nth-child(even) { + background-color: #f2f2f2; +} + +.selected, tr.selected { + background-color: #ffdd99; /* Hervorhebung der Auswahl */ +} + +tr:hover:not(.selected) { + background-color: #ddd; /* Hover-Effekt für nicht ausgewählte Zeilen */ +} + +#navbar { + background-color: #01351d; /* Sehr dunkles Grün */ + color: white; + padding: 10px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 10px; +} + +#navbar h1 { + margin: 0; + font-size: 24px; +} + +#navbar ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +#navbar ul li { + margin-left: 20px; +} + +#navbar a { + color: white; + text-decoration: none; + padding: 8px 15px; + border-radius: 4px; + transition: background-color 0.3s; +} + +#navbar a:hover { + background-color: #007344; /* Etwas helleres Grün */ +} + +#navbar a.active { + background-color: #008C50; /* Mittleres Grün */ +} + + +.content { + background-color: #ebf2eb; /* Helles Grau */ + border-radius: 10px; + padding: 20px; + margin: 20px 0; +} diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000..5d9d04a --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,182 @@ + + + + + Nährwertberechnungs-App + + + + + + + + +
+
+ + + + + + + +
+ + + + + + + + + + + + + +
LebensmittelGewicht (g)kcalEWFettKHBSTCA
+ + + + + + + + + + + + + + + + + + +
kcalEWFettKHBSTCA
000000
+
+ + \ No newline at end of file diff --git a/src/templates/nutrition.html b/src/templates/nutrition.html new file mode 100644 index 0000000..a9a2848 --- /dev/null +++ b/src/templates/nutrition.html @@ -0,0 +1,78 @@ + + + + + Neue Lebensmittel hinzufügen + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
LebensmittelkcalEWFettKHBSTCA
+ +
+
+ + diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..beeb9a0 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +gunicorn 'Run:app' --bind 0.0.0.0:8080 --log-level=info --workers=4 +