containerized

This commit is contained in:
Wolfgang Hottgenroth 2024-01-30 11:54:51 +01:00
parent e72dbad617
commit 8fa433f543
Signed by: wn
GPG Key ID: 836E9E1192A6B132
18 changed files with 932 additions and 0 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
src/__pycache__
src/ENV
src/.venv

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
deployment/secrets.txt
src/.venv
src/__pycache__
src/ENV

32
.woodpecker.yml Normal file
View File

@ -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

15
Dockerfile Normal file
View File

@ -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"

43
deployment/decrypt-secrets.sh Executable file
View File

@ -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

View File

@ -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

43
deployment/deploy.sh Executable file
View File

@ -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

27
deployment/encrypt-secrets.sh Executable file
View File

@ -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

View File

@ -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"
}
}

160
src/Run.py Normal file
View File

@ -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)

36
src/db.py Normal file
View File

@ -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()

86
src/nu.csv Normal file
View File

@ -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
1 name kcal EW Fett KH BST Ca
2 Zitronensaft 38 0.4 0.5 3.8 0.1 11
3 Zucker 405 0.0 0.0 100.0 0.0 2
4 Trinkmilch3.5 65 3.4 3.6 4.7 0.0 120
5 Hühnerei50 137 11.9 9.3 1.5 0.0 51
6 Pflanzenmargarine 722 0.2 80.0 0.4 0.0 8
7 Sahne30 309 2.4 31.7 3.4 0.0 80
8 Maisstärke 353 0.4 0.1 85.9 1.0 0
9 Paniermehl 368 10.1 2.1 73.5 5.3 50
10 Weizengrieß 335 9.6 0.8 69.0 7.1 17
11 Mehl405 343 9.8 1.0 71.8 4.0 15
12 Rapsöl 884 0 100.0 0 0 0
13 Kartoffeln 76 1.9 0.1 15.6 1.2 6
14 Butter 754 0.7 83.2 0.7 0 13
15 Zwiebeln 30 1.2 0.3 4.9 1.4 31
16 Kartoffelstärke 341 0.6 0.1 83.1 0.1 35
17 Olivenöl 884 0 99.8 0.2 0 0
18 Möhre 39 0.8 0.2 6.8 3.6 21
19 Rote linsen 350 23.9 2.2 52.3 10.8 48
20 Gemüsebruhe verz 7 1.6 0 1 0 12
21 Rindfleisch keule 148 20.0 7.6 0 0 6
22 Rinderbouillon 4 0.2 0 1 0 5
23 Meerettich iD 78 2.8 0.3 11.7 7.5 105
24 Saure sahne10 117 3.1 10.0 3.7 0 110
25 Joghurt3.5 64 3.3 3.5 4.4 0 120
26 Dill 65 3.7 0.8 8.0 5.3 230
27 Schnittlauch 40 3.6 0.7 1.6 6.0 129
28 Gartenkresse 41 4.2 0.7 2.5 3.5 214
29 Petersilie 60 4.4 0.4 7.4 4.3 179
30 Mandelstifte 610 18.7 54.1 5.4 13.5 252
31 Porree 29 2.2 0.3 3.3 2.3 63
32 Champignons 20 2.7 0.3 0.6 2.0 10
33 Sellerie 27 1.6 0.3 2.3 4.2 50
34 Apfelsine 47 1.0 0.2 8.3 2.2 42
35 Birne 58 0.5 0.3 12.4 2.8 9
36 Apfel 57 0.3 0.6 11.4 2.0 7
37 Kiwi grün 55 0.9 0.6 9.1 3.0 28
38 Banane 93 1.2 0.2 20.0 1.8 8
39 Schweinefleisch bug 217 17.0 16.5 0.0 0.0 9
40 Gouda48 370 22.7 29.9 0 0 811
41 Blumenkohl 28 2.5 0.3 2.3 2.9 22
42 Knoblauch 145 6.1 0.1 28.4 1.8 38
43 Senf 88 6.0 4.0 6.0 1.0 124
44 Blattspinat roh 21 2.7 0.3 0.6 2.6 117
45 Buttermilch 37 3.5 0.5 4.0 0.0 109
46 Himbeere 37 1.3 0.3 4.8 4.7 40
47 Salz dill gurken 9 0.4 0.1 1.3 0.5 18
48 Schmand20 205 2.8 20.0 3.6 0.0 100
49 Aspikpulver 338 84.2 0.1 0.0 0.0 11
50 Lachs atlantischer 210 20.4 13.4 0.3 0.0 4
51 Pinienkerne 589 24.0 50.7 7.3 7.2 26
52 Zwieback 385 9.9 4.3 73.1 5.2 42
53 Speisequark 72 13.5 0.3 3.2 0.0 92
54 Basilikum 47 3.1 0.8 5.1 3.1 369
55 Mayonaise50 490 0.5 52.0 5.0 0.0 10
56 Weizenbrötchen 292 10.2 1.8 55.9 3.6 49
57 Seelachs köhler 81 18.3 0.9 0 0 14
58 Bohnen grün 21 1.7 0.1 2.0 2.3 34
59 Aprikosen dose 70 0.5 0.1 15.1 1.4 11
60 Orangenfilets 47 1.0 0.2 8.3 2.2 42
61 Mirabellen dose 66 0.7 0.2 15.0 0.9 12
62 Pfirsich dose 67 0.4 0.1 15.5 1.1 4
63 Vanillezucker 405 0.0 0.0 100.0 0 2
64 Vanille pp 346 0.5 0.0 86.0 1 15
65 Brokkoli 34 3.8 0.2 2.7 3.0 58
66 Cornichons 15 1 0.1 2.0 0.8 0
67 Kopfsalat 14 1.2 0.2 1.1 1.4 20
68 Rosenkohl 43 4.5 0.3 3.3 4.4 31
69 Rotkohl 27 1.5 0.2 3.5 2.5 35
70 Spinat 19 2.3 0.3 0.5 2.3 120
71 Tomate 18 1.0 0.2 2.6 1.0 9
72 Tomatenmark 177 9.7 2.0 25.1 1.6 106
73 Wirsing 30 3.0 0.4 2.9 2.5 64
74 Zucchini 23 2.0 0.3 2.3 1.1 25
75 Mais Dose 81 3.2 1.2 12.6 2.8 8
76 Pfefferminze 50 3.8 0.7 5.3 3.0 179
77 Erdbeere 36 0.8 0.4 5.5 2.0 24
78 Himbeere 37 1.3 0.3 4.8 4.7 40
79 Orange/apfelsine 47 1.0 0.2 8.3 2.2 42
80 Orangensaft frisch 44 0.7 0.1 8.7 0.4 11
81 Weintraube 69 0.7 0.3 15.2 1.5 12
82 Naturreis 349 7.2 2.2 74.1 2.2 16
83 Mehl405 343 9.8 1.0 71.8 4.0 15
84 Mehl550 346 9.8 1.1 72.0 4.3 17
85 Milchreis 316 6.4 0.8 80.2 1.1 6
86 Kartoffelstärke 341 0.6 0.1 83.1 0.1 35

BIN
src/nutrition.db Normal file

Binary file not shown.

20
src/requirements.txt Normal file
View File

@ -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

123
src/static/style.css Normal file
View File

@ -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;
}

182
src/templates/index.html Normal file
View File

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Nährwertberechnungs-App</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<script>
// JavaScript-Funktion, um die Produkte beim Laden der Seite zu holen
document.addEventListener('DOMContentLoaded', function() {
fetch('/get_products')
.then(response => response.json())
.then(data => {
const productList = document.getElementById('products');
data.products.forEach(product => {
const option = document.createElement('option');
option.value = product;
productList.appendChild(option);
});
});
const table = document.getElementById('nutrition-table');
table.addEventListener('click', function(e) {
if (e.target.tagName === 'TD') {
// Toggle der Auswahlklasse
e.target.parentNode.classList.toggle('selected');
updateRemoveButtonState();
}
});
});
function updateRemoveButtonState() {
const selectedRows = document.querySelectorAll('#nutrition-table .selected').length;
document.getElementById('remove-button').disabled = selectedRows === 0;
}
function removeSelectedRow() {
const table = document.getElementById('nutrition-table');
Array.from(table.rows).forEach(row => {
if (row.classList.contains('selected')) {
table.deleteRow(row.rowIndex);
}
});
updateRemoveButtonState();
updateTotalNutrition();
}
function updateRemoveButtonState() {
const selectedRows = document.querySelectorAll('#nutrition-table .selected').length;
document.getElementById('remove-button').disabled = selectedRows === 0;
}
// JavaScript-Funktionen
function updateButtonState() {
const food = document.getElementById('my_combobox').value;
const weight = document.getElementById('weight').value;
document.getElementById('submit-button').disabled = !(food && weight);
}
function addProduct() {
const foodInput = document.getElementById('my_combobox');
const weightInput = document.getElementById('weight');
const food = foodInput.value;
const weight = weightInput.value;
fetch(`/add_lm?food=${encodeURIComponent(food)}&weight=${encodeURIComponent(weight)}`)
.then(response => response.json())
.then(data => {
const table = document.getElementById('nutrition-table');
const row = table.insertRow();
// Fügt die einzelnen Nährwerte zur neuen Zeile hinzu
row.insertCell(0).innerHTML = food;
row.insertCell(1).innerHTML = weight;
row.insertCell(2).innerHTML = data.kcal;
row.insertCell(3).innerHTML = data.ew;
row.insertCell(4).innerHTML = data.fett;
row.insertCell(5).innerHTML = data.kh;
row.insertCell(6).innerHTML = data.bst;
row.insertCell(7).innerHTML = data.ca;
foodInput.value = ''; // Zurücksetzen des Lebensmittel-Eingabefeldes
weightInput.value = ''; // Zurücksetzen des Gewicht-Eingabefeldes
document.getElementById('submit-button').disabled = true; // Deaktivieren des Hinzufügen-Buttons
updateTotalNutrition();
})
.catch(error => console.error('Fehler:', error));
}
function updateTotalNutrition() {
let totalWeight = 0, totalKcal = 0, totalEw = 0, totalFett = 0, totalKh = 0, totalBst = 0, totalCa = 0;
// Durchlaufen aller Zeilen in der Haupttabelle und Addition der Werte
Array.from(document.getElementById('nutrition-table').rows).slice(1).forEach(row => {
totalKcal += parseFloat(row.cells[2].innerText);
totalEw += parseFloat(row.cells[3].innerText);
totalFett += parseFloat(row.cells[4].innerText);
totalKh += parseFloat(row.cells[5].innerText);
totalBst += parseFloat(row.cells[6].innerText);
totalCa += parseFloat(row.cells[7].innerText);
});
// Rundung und Aktualisierung der Gesamtwerte
document.getElementById('total-kcal').innerText = Math.round(totalKcal);
document.getElementById('total-ew').innerText = totalEw.toFixed(1);
document.getElementById('total-fett').innerText = totalFett.toFixed(1);
document.getElementById('total-kh').innerText = totalKh.toFixed(1);
document.getElementById('total-bst').innerText = totalBst.toFixed(1);
document.getElementById('total-ca').innerText = Math.round(totalCa);
}
// Rufen Sie diese Funktion auf, wenn sich die Haupttabelle ändert
// Diese Funktion sollte aufgerufen werden, wenn ein Produkt hinzugefügt oder entfernt wird
</script>
</head>
<body>
<nav id="navbar">
<h1>Elos Rezept Rechner</h1>
<ul>
<li><a href="/" class="active">Rechner</a></li>
<li><a href="/nutrition">Neue Lebensmittel</a></li>
</ul>
</nav>
<div class="content">
<form onsubmit="event.preventDefault(); addProduct();" id="product-form">
<label for="my_combobox">Wählen Sie ein Lebensmittel</label>
<input list="products" name="my_combobox" id="my_combobox" oninput="updateButtonState()" autocomplete="off">
<datalist id="products">
<!-- Produkte werden hier dynamisch eingefügt -->
</datalist>
<input type="number" id="weight" name="weight" placeholder="Gramm" oninput="updateButtonState()">
<button type="submit" id="submit-button" disabled>Hinzufügen</button>
</form>
<table id="nutrition-table">
<tr>
<th>Lebensmittel</th>
<th>Gewicht (g)</th>
<th>kcal</th>
<th>EW</th>
<th>Fett</th>
<th>KH</th>
<th>BST</th>
<th>CA</th>
</tr>
<!-- Zeilen werden hier dynamisch hinzugefügt -->
</table>
<button id="remove-button" onclick="removeSelectedRow()" disabled>Entfernen</button>
<table id="total-nutrition-table">
<tr>
<th>kcal</th>
<th>EW</th>
<th>Fett</th>
<th>KH</th>
<th>BST</th>
<th>CA</th>
</tr>
<tr>
<td id="total-kcal">0</td>
<td id="total-ew">0</td>
<td id="total-fett">0</td>
<td id="total-kh">0</td>
<td id="total-bst">0</td>
<td id="total-ca">0</td>
</tr>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Neue Lebensmittel hinzufügen</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<script>
function updateSubmitButtonState() {
const inputs = document.querySelectorAll('#nutrition-form input');
const allFilled = Array.from(inputs).every(input => input.value.trim() !== '');
document.getElementById('submit-button').disabled = !allFilled;
}
function addNutritionEntry() {
const form = document.getElementById('nutrition-form');
const formData = new FormData(form);
fetch('/add_nutrition', {
method: 'POST',
body: formData
}).then(response => {
// Behandlung der Serverantwort
// Beispielsweise das Formular zurücksetzen
form.reset();
updateSubmitButtonState();
}).catch(error => {
console.error('Fehler:', error);
});
// ... Code, um den Eintrag zur Datenbank hinzuzufügen
// Nach dem Hinzufügen, setze alle Eingabefelder zurück
document.querySelectorAll('#nutrition-form input').forEach(input => {
input.value = ''; // Setzt den Wert jedes Eingabefeldes zurück
});
// Deaktiviere den "Hinzufügen"-Button wieder
document.getElementById('submit-button').disabled = true;
}
</script>
</head>
<body>
<nav id="navbar">
<h1>Elos Rezept Rechner</h1>
<ul>
<li><a href="/">Rechner</a></li>
<li><a href="/nutrition" class="active">Neue Lebensmittel</a></li>
</ul>
</nav>
<div class="content">
<form onsubmit="event.preventDefault(); addNutritionEntry();" method="POST" id="nutrition-form">
<table id="nutrition-input-table">
<tr>
<th>Lebensmittel</th>
<th>kcal</th>
<th>EW</th>
<th>Fett</th>
<th>KH</th>
<th>BST</th>
<th>CA</th>
</tr>
<tr>
<td><input type="text" name="food" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="kcal" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="ew" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="fett" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="kh" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="bst" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
<td><input <input type="text" name="ca" pattern="\d+(\.\d{1,2})?" oninput="updateSubmitButtonState()"></td>
</tr>
</table>
<button type="submit" id="submit-button" disabled>Hinzufügen</button>
</form>
</div>
</body>
</html>

4
start.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
gunicorn 'Run:app' --bind 0.0.0.0:8080 --log-level=info --workers=4