Initial commit: infra + flask + openproject + gitea
BIN
._.gitignore
Normal file
181
.gitignore
vendored
@ -1,162 +1,39 @@
|
|||||||
# ---> Python
|
# Python
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.pyc
|
||||||
*$py.class
|
*.pyo
|
||||||
|
|
||||||
# C extensions
|
# Env / secrets
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
#pdm.lock
|
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
|
||||||
.pdm.toml
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
.env.*
|
||||||
env/
|
!.env.example
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
# Logs
|
||||||
.spyderproject
|
*.log
|
||||||
.spyproject
|
logs/
|
||||||
|
container-logs/
|
||||||
|
|
||||||
# Rope project settings
|
# macOS
|
||||||
.ropeproject
|
.DS_Store
|
||||||
|
|
||||||
# mkdocs documentation
|
# VS Code
|
||||||
/site
|
.vscode/
|
||||||
|
|
||||||
# mypy
|
# Runtime data
|
||||||
.mypy_cache/
|
db/
|
||||||
.dmypy.json
|
pgdata/
|
||||||
dmypy.json
|
assets/
|
||||||
|
data/
|
||||||
|
tmp/
|
||||||
|
|
||||||
# Pyre type checker
|
# OpenProject / Gitea persistent data
|
||||||
.pyre/
|
infra/openproject/pgdata/
|
||||||
|
infra/openproject/assets/
|
||||||
|
infra/gitea/data/
|
||||||
|
|
||||||
# pytype static type analyzer
|
# Flask runtime
|
||||||
.pytype/
|
app/flask-postgres/files/uploads/
|
||||||
|
app/flask-postgres/files/runtime/
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
|
# Synology
|
||||||
|
@eaDir/
|
||||||
BIN
app/flask-postgres/._Kurzdoku.txt
Normal file
16
app/flask-postgres/Kurzdoku.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
Kurzdoku FLASK Certification Validation
|
||||||
|
|
||||||
|
Postgres DB:
|
||||||
|
DB_HOST: db
|
||||||
|
DB_NAME: CertDB
|
||||||
|
DB_USER: CertUser
|
||||||
|
DB_PASSWORD: CertPWD
|
||||||
|
DB_PORT: 5432
|
||||||
|
|
||||||
|
app.py
|
||||||
|
|
||||||
|
Postgres admin@kolb.cc
|
||||||
|
pwd DBadmin
|
||||||
|
Port 5051
|
||||||
|
http://192.168.0.10:5051/browser/
|
||||||
|
|
||||||
BIN
app/flask-postgres/app/._Dockerfile
Normal file
BIN
app/flask-postgres/app/._app.py
Normal file
BIN
app/flask-postgres/app/._requirements.txt
Normal file
BIN
app/flask-postgres/app/._templates
Normal file
12
app/flask-postgres/app/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--access-logfile", "/logs/gunicorn-access.log", "--error-logfile", "/logs/gunicorn-error.log", "app:app"]
|
||||||
327
app/flask-postgres/app/app.py
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
request,
|
||||||
|
send_from_directory,
|
||||||
|
session,
|
||||||
|
url_for,
|
||||||
|
)
|
||||||
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = os.getenv("SECRET_KEY", "change-this-secret-key")
|
||||||
|
|
||||||
|
DB_HOST = os.getenv("DB_HOST", "db")
|
||||||
|
DB_NAME = os.getenv("DB_NAME", "CertDB")
|
||||||
|
DB_USER = os.getenv("DB_USER", "CertUser")
|
||||||
|
DB_PASSWORD = os.getenv("DB_PASSWORD", "CertPWD")
|
||||||
|
DB_PORT = os.getenv("DB_PORT", "5432")
|
||||||
|
|
||||||
|
LOG_DIR = os.getenv("LOG_DIR", "/logs")
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
os.path.join(LOG_DIR, "flask-app.log"),
|
||||||
|
maxBytes=5 * 1024 * 1024,
|
||||||
|
backupCount=5
|
||||||
|
)
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
file_handler.setFormatter(
|
||||||
|
logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||||
|
)
|
||||||
|
|
||||||
|
app.logger.setLevel(logging.INFO)
|
||||||
|
app.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
return psycopg2.connect(
|
||||||
|
host=DB_HOST,
|
||||||
|
dbname=DB_NAME,
|
||||||
|
user=DB_USER,
|
||||||
|
password=DB_PASSWORD,
|
||||||
|
port=DB_PORT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_base_tables():
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS visits (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
route_name VARCHAR(100),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS mandant (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
kuerzel VARCHAR(50) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
kontakt_email VARCHAR(255),
|
||||||
|
level INTEGER NOT NULL DEFAULT 0
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS app_user (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
mandant_id INTEGER NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
last_login TIMESTAMP NULL,
|
||||||
|
status INTEGER NOT NULL DEFAULT 0,
|
||||||
|
CONSTRAINT fk_app_user_mandant
|
||||||
|
FOREIGN KEY (mandant_id)
|
||||||
|
REFERENCES mandant(id)
|
||||||
|
ON DELETE RESTRICT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS app_group (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
mandant_id INTEGER NOT NULL,
|
||||||
|
group_name VARCHAR(255) NOT NULL,
|
||||||
|
CONSTRAINT fk_app_group_mandant
|
||||||
|
FOREIGN KEY (mandant_id)
|
||||||
|
REFERENCES mandant(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS user_group (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
mandant_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, group_id),
|
||||||
|
CONSTRAINT fk_user_group_user
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES app_user(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_user_group_group
|
||||||
|
FOREIGN KEY (group_id)
|
||||||
|
REFERENCES app_group(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_user_group_mandant
|
||||||
|
FOREIGN KEY (mandant_id)
|
||||||
|
REFERENCES mandant(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_default_admin():
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("SELECT id FROM mandant WHERE kuerzel = %s", ("KOLB",))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
mandant_id = row[0]
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO mandant (kuerzel, name, kontakt_email, level)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
RETURNING id
|
||||||
|
""", ("KOLB", "Kolb Compliance", "info@kolb.cc", 0))
|
||||||
|
mandant_id = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.execute("SELECT id FROM app_user WHERE email = %s", ("admin@kolb.cc",))
|
||||||
|
user_row = cur.fetchone()
|
||||||
|
|
||||||
|
if not user_row:
|
||||||
|
password_hash = generate_password_hash("topsecret")
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO app_user (email, name, mandant_id, password_hash, status)
|
||||||
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
|
""", ("admin@kolb.cc", "Admin", mandant_id, password_hash, 1))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def register_visit(route_name: str) -> int:
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO visits (route_name) VALUES (%s)",
|
||||||
|
(route_name,)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cur.execute("SELECT COUNT(*) FROM visits")
|
||||||
|
count = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_user():
|
||||||
|
return {
|
||||||
|
"user_id": session.get("user_id"),
|
||||||
|
"user_name": session.get("user_name"),
|
||||||
|
"user_email": session.get("user_email"),
|
||||||
|
"is_logged_in": bool(session.get("user_id")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def login_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if not session.get("user_id"):
|
||||||
|
return redirect(url_for("login", next=request.path))
|
||||||
|
return view_func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(active_page: str, title: str):
|
||||||
|
count = register_visit(active_page)
|
||||||
|
return render_template(
|
||||||
|
"index.html",
|
||||||
|
active_page=active_page,
|
||||||
|
page_title=title,
|
||||||
|
visit_count=count,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def startup_checks():
|
||||||
|
ensure_base_tables()
|
||||||
|
ensure_default_admin()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
@app.route("/home")
|
||||||
|
def home():
|
||||||
|
return render_page("home", "Home")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/preise")
|
||||||
|
@login_required
|
||||||
|
def preise():
|
||||||
|
return render_page("preise", "Preise")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/allgemein")
|
||||||
|
@login_required
|
||||||
|
def allgemein():
|
||||||
|
return render_page("allgemein", "Allgemein")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
|
def login():
|
||||||
|
error_message = ""
|
||||||
|
next_url = request.args.get("next") or request.form.get("next") or url_for("preise")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
email = request.form.get("email", "").strip().lower()
|
||||||
|
password = request.form.get("password", "")
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id, email, name, password_hash, status
|
||||||
|
FROM app_user
|
||||||
|
WHERE lower(email) = %s
|
||||||
|
""", (email,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
error_message = "Benutzer nicht gefunden."
|
||||||
|
else:
|
||||||
|
user_id, user_email, user_name, password_hash, status = row
|
||||||
|
|
||||||
|
if status == 0:
|
||||||
|
error_message = "Benutzer ist noch nicht aktiviert."
|
||||||
|
elif status == 2:
|
||||||
|
error_message = "Benutzer ist gesperrt."
|
||||||
|
elif status == 3:
|
||||||
|
error_message = "Benutzer ist deaktiviert."
|
||||||
|
elif not check_password_hash(password_hash, password):
|
||||||
|
error_message = "Passwort ist falsch."
|
||||||
|
else:
|
||||||
|
session["user_id"] = user_id
|
||||||
|
session["user_email"] = user_email
|
||||||
|
session["user_name"] = user_name
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE app_user
|
||||||
|
SET last_login = %s
|
||||||
|
WHERE id = %s
|
||||||
|
""", (datetime.utcnow(), user_id))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
app.logger.info("Login erfolgreich: %s", user_email)
|
||||||
|
return redirect(next_url)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"login.html",
|
||||||
|
page_title="Login",
|
||||||
|
active_page="login",
|
||||||
|
error_message=error_message,
|
||||||
|
next_url=next_url,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
user_email = session.get("user_email", "unknown")
|
||||||
|
session.clear()
|
||||||
|
app.logger.info("Logout: %s", user_email)
|
||||||
|
return redirect(url_for("home"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/health")
|
||||||
|
def health():
|
||||||
|
try:
|
||||||
|
ensure_base_tables()
|
||||||
|
return "OK\n", 200
|
||||||
|
except Exception as exc:
|
||||||
|
app.logger.exception("Healthcheck fehlgeschlagen: %s", exc)
|
||||||
|
return f"DB Fehler: {exc}\n", 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/images/<path:filename>")
|
||||||
|
def serve_image(filename):
|
||||||
|
return send_from_directory("/app/images", filename)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/styles/<path:filename>")
|
||||||
|
def serve_style(filename):
|
||||||
|
return send_from_directory("/app/styles", filename)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/files/<path:filename>")
|
||||||
|
def serve_file(filename):
|
||||||
|
return send_from_directory("/app/files", filename)
|
||||||
4
app/flask-postgres/app/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Flask==3.0.2
|
||||||
|
gunicorn==22.0.0
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
Werkzeug==3.0.1
|
||||||
BIN
app/flask-postgres/app/templates/._.DS_Store
Normal file
BIN
app/flask-postgres/app/templates/._index.html
Normal file
BIN
app/flask-postgres/app/templates/._login.html
Normal file
BIN
app/flask-postgres/app/templates/._preise.html
Normal file
45
app/flask-postgres/app/templates/index.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner">
|
||||||
|
<div class="logo-area">
|
||||||
|
<a href="/home">
|
||||||
|
<img src="/images/Logo-Compliance-Verification-bg-1.png" alt="Logo" class="site-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a href="/home" class="{% if active_page == 'home' %}active{% endif %}">Home</a>
|
||||||
|
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
||||||
|
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<span class="user-box">{{ user_name }}</span>
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/login" class="{% if active_page == 'login' %}active{% endif %}">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="content-box">
|
||||||
|
{% if active_page == "home" %}
|
||||||
|
{% include "partials/home_content.html" %}
|
||||||
|
{% elif active_page == "preise" %}
|
||||||
|
{% include "partials/preise_content.html" %}
|
||||||
|
{% elif active_page == "allgemein" %}
|
||||||
|
{% include "partials/allgemein_content.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
62
app/flask-postgres/app/templates/login.html
Normal 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>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner">
|
||||||
|
<div class="logo-area">
|
||||||
|
<a href="/home">
|
||||||
|
<img src="/images/Logo-Compliance-Verification-bg-1.png" alt="Logo" class="site-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a href="/home">Home</a>
|
||||||
|
<a href="/preise">Preise</a>
|
||||||
|
<a href="/allgemein">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<span class="user-box">{{ user_name }}</span>
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/login" class="active">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="content-box login-box">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<p class="intro-text">Bitte melden Sie sich an, um auf geschützte Inhalte zuzugreifen.</p>
|
||||||
|
|
||||||
|
{% if error_message %}
|
||||||
|
<div class="error-box">{{ error_message }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="/login" class="login-form">
|
||||||
|
<input type="hidden" name="next" value="{{ next_url }}">
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="email">E-Mail</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="password">Passwort</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<button type="submit" class="btn-primary">Anmelden</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
app/flask-postgres/app/templates/partials/._home_content.html
Normal file
BIN
app/flask-postgres/app/templates/partials/._preise_content.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<div class="hero-box">
|
||||||
|
<h1>Compliance Verification</h1>
|
||||||
|
<p>
|
||||||
|
KI sicher, rechtskonform und verantwortungsvoll im Unternehmen einsetzen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>Warum Compliance Verification?</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Der Einsatz von Künstlicher Intelligenz bringt enorme Chancen – aber auch
|
||||||
|
rechtliche und organisatorische Risiken.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Mit unserem Ansatz stellen Sie sicher, dass Ihre Organisation die Anforderungen
|
||||||
|
des EU AI Act erfüllt und gleichzeitig effizient und sicher arbeitet.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>Was wir bieten</h2>
|
||||||
|
|
||||||
|
<ul class="check-list">
|
||||||
|
<li>Rechtssichere Schulungen für Mitarbeitende</li>
|
||||||
|
<li>Strukturierte KI-Governance</li>
|
||||||
|
<li>Dokumentation und Audit-Fähigkeit</li>
|
||||||
|
<li>Risikobewertung und Kontrolle</li>
|
||||||
|
<li>Praxisnahe Umsetzung im Unternehmen</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>Für wen ist das relevant?</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Unsere Lösungen richten sich an Unternehmen jeder Größe, die KI einsetzen oder
|
||||||
|
dies planen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="check-list">
|
||||||
|
<li>Geschäftsführung und Management</li>
|
||||||
|
<li>IT- und Compliance-Abteilungen</li>
|
||||||
|
<li>HR und Fachbereiche</li>
|
||||||
|
<li>Organisationen mit regulatorischen Anforderungen</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>Ihr Vorteil</h2>
|
||||||
|
|
||||||
|
<div class="two-col">
|
||||||
|
<div>
|
||||||
|
<ul class="check-list">
|
||||||
|
<li>Rechtssicherheit</li>
|
||||||
|
<li>Reduziertes Risiko</li>
|
||||||
|
<li>Strukturierte Prozesse</li>
|
||||||
|
<li>Nachweisbare Compliance</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="image-panel">
|
||||||
|
<img src="/images/Schulung.png" alt="Compliance Schulung">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>Jetzt starten</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Beginnen Sie mit einer fundierten Grundlage und entwickeln Sie Ihre Organisation
|
||||||
|
Schritt für Schritt zur vollständigen KI-Compliance.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="/preise" class="btn-primary">
|
||||||
|
Zu den Preisen
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<div class="hero-box">
|
||||||
|
<h1>Compliance Verification</h1>
|
||||||
|
<p>Willkommen auf der Startseite Ihrer Flask-Anwendung im Kolb-Layout.</p>
|
||||||
|
<p>Besuche insgesamt: {{ visit_count }}</p>
|
||||||
|
<p>Für die Seiten <strong>Preise</strong> und <strong>Allgemein</strong> ist eine Anmeldung erforderlich.</p>
|
||||||
|
</div>
|
||||||
123
app/flask-postgres/app/templates/partials/preise_content.html
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<div class="hero-box">
|
||||||
|
<h1>Klare Preise für Ihre KI-Compliance</h1>
|
||||||
|
<p>Wählen Sie zwischen sofort buchbaren Modulen und individueller Unternehmenslösung.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="pricing-section">
|
||||||
|
<h2>Leistungen und Preise</h2>
|
||||||
|
|
||||||
|
<div class="pricing-grid">
|
||||||
|
<article class="price-card">
|
||||||
|
<h3>Modul A – Essential</h3>
|
||||||
|
<p class="price-subline">Für alle Mitarbeitenden – gesetzliche Mindestanforderung</p>
|
||||||
|
<div class="price-value">€ 9,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Erfüllt die gesetzliche Schulungspflicht</li>
|
||||||
|
<li>5 Pflichtmodule</li>
|
||||||
|
<li>AI Governance Basics</li>
|
||||||
|
<li>Ethical AI & Non-Discrimination</li>
|
||||||
|
<li>AI Risk Awareness</li>
|
||||||
|
<li>Transparency & Disclosure</li>
|
||||||
|
<li>Documentation & Prozesse</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="price-card featured">
|
||||||
|
<h3>Modul B – Compliance</h3>
|
||||||
|
<p class="price-subline">Für Fachbereiche – rechtssichere KI-Nutzung im Unternehmen</p>
|
||||||
|
<div class="price-value">€ 19,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Alle Module aus A</li>
|
||||||
|
<li>10 Module insgesamt</li>
|
||||||
|
<li>Datenschutz & KI</li>
|
||||||
|
<li>AI Use Policies</li>
|
||||||
|
<li>Copyright & Trainingsdaten</li>
|
||||||
|
<li>Audit Readiness</li>
|
||||||
|
<li>Organisation & Verantwortlichkeiten</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="price-card">
|
||||||
|
<h3>Modul C – Governance</h3>
|
||||||
|
<p class="price-subline">Für Unternehmen – vollständige Steuerung und Absicherung</p>
|
||||||
|
<div class="price-value">€ 29,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Alle Module aus A & B</li>
|
||||||
|
<li>15 Module insgesamt</li>
|
||||||
|
<li>Advanced Copyright & IP Strategy</li>
|
||||||
|
<li>Security & Misuse Prevention</li>
|
||||||
|
<li>Vendor & Tool Governance</li>
|
||||||
|
<li>Risk Mapping</li>
|
||||||
|
<li>Individuelle Beratung</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="overview-section">
|
||||||
|
<h2>Welches Paket ist das richtige?</h2>
|
||||||
|
<div class="image-panel">
|
||||||
|
<img src="/images/TabelleUebersicht.png" alt="Paketübersicht">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="company-section">
|
||||||
|
<div class="two-col">
|
||||||
|
<div>
|
||||||
|
<h2>Für ganze Unternehmen</h2>
|
||||||
|
<p>Sie möchten Ihre gesamte Organisation rechtssicher aufstellen?</p>
|
||||||
|
<ul class="check-list">
|
||||||
|
<li>individuelle Risikoanalyse</li>
|
||||||
|
<li>unternehmensweite Umsetzung</li>
|
||||||
|
<li>rechtssichere Dokumentation</li>
|
||||||
|
<li>kontinuierliche Begleitung</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="image-panel">
|
||||||
|
<img src="/images/Schulung.png" alt="Schulung">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="modules-section">
|
||||||
|
<h2>Module / geprüfte Bereiche</h2>
|
||||||
|
|
||||||
|
<div class="module-blocks">
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Basis-Level</h3>
|
||||||
|
<p class="module-result">AI-Safe Workforce</p>
|
||||||
|
<ul>
|
||||||
|
<li>AI Governance Basics</li>
|
||||||
|
<li>Ethical AI & Non-Discrimination</li>
|
||||||
|
<li>AI Risk & Impact Awareness</li>
|
||||||
|
<li>Transparency & Customer Disclosure</li>
|
||||||
|
<li>Documentation & Internal Processes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Compliance-Level</h3>
|
||||||
|
<p class="module-result">AI Compliance Ready</p>
|
||||||
|
<ul>
|
||||||
|
<li>Datenschutz & KI</li>
|
||||||
|
<li>KI-Nutzung & HR-Policies</li>
|
||||||
|
<li>Urheberrecht & Trainingsdaten</li>
|
||||||
|
<li>Erweiterte Dokumentation & Transparenz</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Governance-Level</h3>
|
||||||
|
<p class="module-result">Full AI Governance</p>
|
||||||
|
<ul>
|
||||||
|
<li>Copyright Deep Dive</li>
|
||||||
|
<li>Security & Missbrauchsprävention</li>
|
||||||
|
<li>Vendor & Tool-Prüfung</li>
|
||||||
|
<li>Individuelle Risikoanalyse & Beratung</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
190
app/flask-postgres/app/templates/preise.html
Normal 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>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner">
|
||||||
|
<div class="logo-area">
|
||||||
|
<a href="/home">
|
||||||
|
<img src="/images/Logo-Compliance-Verification-bg-1.png" alt="Logo" class="site-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a href="/home" class="{% if active_page == 'home' %}active{% endif %}">Home</a>
|
||||||
|
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
||||||
|
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<span class="user-box">{{ user_name }}</span>
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="hero-box">
|
||||||
|
<h1>Klare Preise für Ihre KI-Compliance</h1>
|
||||||
|
<p>Wählen Sie zwischen sofort buchbaren Modulen und individueller Unternehmenslösung.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="pricing-section">
|
||||||
|
<h2>Leistungen und Preise</h2>
|
||||||
|
|
||||||
|
<div class="pricing-grid">
|
||||||
|
<article class="price-card">
|
||||||
|
<h3>Modul A – Essential</h3>
|
||||||
|
<p class="price-subline">Für alle Mitarbeitenden – gesetzliche Mindestanforderung</p>
|
||||||
|
<div class="price-value">€ 9,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Erfüllt die gesetzliche Schulungspflicht (EU AI Act)</li>
|
||||||
|
<li>5 Pflichtmodule</li>
|
||||||
|
<li>AI Governance Basics</li>
|
||||||
|
<li>Ethical AI & Non-Discrimination</li>
|
||||||
|
<li>AI Risk Awareness</li>
|
||||||
|
<li>Transparency & Disclosure</li>
|
||||||
|
<li>Documentation & Prozesse (Basic)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a href="#" class="btn-primary">Jetzt buchen</a>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="price-card featured">
|
||||||
|
<h3>Modul B – Compliance</h3>
|
||||||
|
<p class="price-subline">Für Fachbereiche – rechtssichere KI-Nutzung im Unternehmen</p>
|
||||||
|
<div class="price-value">€ 19,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Alle Module aus A</li>
|
||||||
|
<li>10 Module insgesamt</li>
|
||||||
|
<li>Data & Privacy Compliance</li>
|
||||||
|
<li>AI Use Policies & Unternehmensrichtlinien</li>
|
||||||
|
<li>Copyright & AI (Basic)</li>
|
||||||
|
<li>Dokumentation & Audit Readiness</li>
|
||||||
|
<li>Organisation & Verantwortlichkeiten</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a href="#" class="btn-primary">Jetzt buchen</a>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="price-card">
|
||||||
|
<h3>Modul C – Governance</h3>
|
||||||
|
<p class="price-subline">Für Unternehmen – vollständige Steuerung, Kontrolle und Absicherung</p>
|
||||||
|
<div class="price-value">€ 29,90 <span>/ jährlich pro Nutzer</span></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Alle Module aus A & B</li>
|
||||||
|
<li>15 Module insgesamt</li>
|
||||||
|
<li>Advanced Copyright & IP Strategy</li>
|
||||||
|
<li>Security & Misuse Prevention</li>
|
||||||
|
<li>Vendor & Tool Governance</li>
|
||||||
|
<li>Risk Mapping & Governance Framework</li>
|
||||||
|
<li>Umsetzung & individuelle Beratung</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a href="#" class="btn-primary">Jetzt buchen</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="price-note">
|
||||||
|
Alle Preise verstehen sich inklusive Mehrwertsteuer.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="overview-section">
|
||||||
|
<h2>Welches Paket ist das richtige für Ihr Unternehmen?</h2>
|
||||||
|
<div class="image-panel">
|
||||||
|
<img src="/images/TabelleUebersicht.png" alt="Paketübersicht">
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Die meisten Unternehmen starten mit Modul A und erweitern anschließend auf Modul B oder C – je nach Einsatz von KI und Risikoprofil.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="company-section">
|
||||||
|
<div class="two-col">
|
||||||
|
<div>
|
||||||
|
<h2>Für ganze Unternehmen</h2>
|
||||||
|
<p>Sie möchten nicht nur einzelne Mitarbeitende schulen, sondern Ihre gesamte Organisation rechtssicher aufstellen?</p>
|
||||||
|
<ul class="check-list">
|
||||||
|
<li>individuelle Risikoanalyse</li>
|
||||||
|
<li>unternehmensweite Umsetzung</li>
|
||||||
|
<li>rechtssichere Dokumentation</li>
|
||||||
|
<li>kontinuierliche Begleitung</li>
|
||||||
|
</ul>
|
||||||
|
<a href="#" class="btn-secondary">Individuelles Angebot anfragen</a>
|
||||||
|
</div>
|
||||||
|
<div class="image-panel">
|
||||||
|
<img src="/images/Schulung.png" alt="Schulung">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="modules-section">
|
||||||
|
<h2>Module / geprüfte Bereiche</h2>
|
||||||
|
<div class="module-blocks">
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Basis-Level</h3>
|
||||||
|
<p class="module-result">AI-Safe Workforce</p>
|
||||||
|
<ul>
|
||||||
|
<li>AI Governance Basics</li>
|
||||||
|
<li>Ethical AI & Non-Discrimination</li>
|
||||||
|
<li>AI Risk & Impact Awareness</li>
|
||||||
|
<li>Transparency & Customer Disclosure</li>
|
||||||
|
<li>Documentation & Internal Processes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Compliance-Level</h3>
|
||||||
|
<p class="module-result">AI Compliance Ready</p>
|
||||||
|
<ul>
|
||||||
|
<li>Datenschutz & KI</li>
|
||||||
|
<li>KI-Nutzung & HR-Policies</li>
|
||||||
|
<li>Urheberrecht & Trainingsdaten</li>
|
||||||
|
<li>Erweiterte Dokumentation & Transparenz</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-block">
|
||||||
|
<h3>Governance-Level</h3>
|
||||||
|
<p class="module-result">Full AI Governance</p>
|
||||||
|
<ul>
|
||||||
|
<li>Copyright Deep Dive</li>
|
||||||
|
<li>Security & Missbrauchsprävention</li>
|
||||||
|
<li>Vendor & Tool-Prüfung</li>
|
||||||
|
<li>Individuelle Risikoanalyse & Beratung</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="steps-section">
|
||||||
|
<h2>So funktioniert Compliance Verification</h2>
|
||||||
|
<div class="steps-grid">
|
||||||
|
<div class="step-card">
|
||||||
|
<h3>Schritt 1</h3>
|
||||||
|
<p>Sie wählen Module und starten sofort.</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-card">
|
||||||
|
<h3>Schritt 2</h3>
|
||||||
|
<p>Ihre Mitarbeitenden absolvieren Schulung und Prüfung.</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-card">
|
||||||
|
<h3>Schritt 3</h3>
|
||||||
|
<p>Sie erhalten ein belastbares Compliance-Zertifikat.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
app/flask-postgres/images/._.DS_Store
Normal file
BIN
app/flask-postgres/images/._142815-780943566_small.mp4
Normal file
BIN
app/flask-postgres/images/._TabelleUebersicht.png
Normal file
BIN
app/flask-postgres/images/._ai-governance.png
Normal file
BIN
app/flask-postgres/images/._cloud-security.png
Normal file
BIN
app/flask-postgres/images/._hero-legal-ai.png
Normal file
BIN
app/flask-postgres/images/._logo-compliance.png
Normal file
BIN
app/flask-postgres/images/._schulung.png
Normal file
BIN
app/flask-postgres/images/._security-shield.png
Normal file
BIN
app/flask-postgres/images/142815-780943566_small.mp4
Normal file
BIN
app/flask-postgres/images/Logo-Compliance-Verification-bg-1.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
app/flask-postgres/images/TabelleUebersicht.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
app/flask-postgres/images/ai-governance.png
Normal file
|
After Width: | Height: | Size: 595 KiB |
BIN
app/flask-postgres/images/cloud-security.png
Normal file
|
After Width: | Height: | Size: 606 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/flask-postgres/images/hero-legal-ai.png
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
app/flask-postgres/images/logo-compliance.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
app/flask-postgres/images/schulung.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
app/flask-postgres/images/security-shield.png
Normal file
|
After Width: | Height: | Size: 566 KiB |
BIN
app/flask-postgres/images/videos/._A1 AI Governance.mp4
Normal file
BIN
app/flask-postgres/images/videos/._A2 ethical AI.mp4
Normal file
BIN
app/flask-postgres/images/videos/._A3 Risk awareness.mp4
Normal file
BIN
app/flask-postgres/images/videos/._A4 Transparency.mp4
Normal file
BIN
app/flask-postgres/images/videos/._A5 internal Processes.mp4
Normal file
BIN
app/flask-postgres/images/videos/._B1 Privacy.mp4
Normal file
BIN
app/flask-postgres/images/videos/._B2 Policies.mp4
Normal file
BIN
app/flask-postgres/images/videos/._B3 Copyright.mp4
Normal file
BIN
app/flask-postgres/images/videos/._B4 Documentation.mp4
Normal file
BIN
app/flask-postgres/images/videos/._B5 organisation.mp4
Normal file
BIN
app/flask-postgres/images/videos/._C1 Advanced Copyright.mp4
Normal file
BIN
app/flask-postgres/images/videos/._C2 misuse.mp4
Normal file
BIN
app/flask-postgres/images/videos/A1 AI Governance.mp4
Normal file
BIN
app/flask-postgres/images/videos/A2 ethical AI.mp4
Normal file
BIN
app/flask-postgres/images/videos/A3 Risk awareness.mp4
Normal file
BIN
app/flask-postgres/images/videos/A4 Transparency.mp4
Normal file
BIN
app/flask-postgres/images/videos/A5 internal Processes.mp4
Normal file
BIN
app/flask-postgres/images/videos/B1 Privacy.mp4
Normal file
BIN
app/flask-postgres/images/videos/B2 Policies.mp4
Normal file
BIN
app/flask-postgres/images/videos/B3 Copyright.mp4
Normal file
BIN
app/flask-postgres/images/videos/B4 Documentation.mp4
Normal file
BIN
app/flask-postgres/images/videos/B5 organisation.mp4
Normal file
BIN
app/flask-postgres/images/videos/C1 Advanced Copyright.mp4
Normal file
BIN
app/flask-postgres/images/videos/C2 misuse.mp4
Normal file
BIN
app/flask-postgres/styles/._site.css
Normal file
326
app/flask-postgres/styles/site.css
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/* =========================
|
||||||
|
RESET & BASE
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(180deg, #07192d 0%, #102a45 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
HEADER
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
background: rgba(4, 18, 33, 0.96);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-inner {
|
||||||
|
max-width: 1320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 14px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
LOGO
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
height: 60px;
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
NAVIGATION
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.top-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav a {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 999px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
min-width: 110px; /* verhindert Springen */
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav a:hover,
|
||||||
|
.top-nav a.active {
|
||||||
|
background: #376da6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-box {
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
MAIN CONTENT
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-box {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
background: #ffffff;
|
||||||
|
color: #1b2430;
|
||||||
|
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
|
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
HEADINGS
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #0d2f57;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #2f3b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
HERO
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.hero-box {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
PRICING
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.pricing-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #dce3ea;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-card.featured {
|
||||||
|
border: 2px solid #1d66b2;
|
||||||
|
background: #f5f9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-card h3 {
|
||||||
|
color: #0d2f57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-subline {
|
||||||
|
color: #526172;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #125eb0;
|
||||||
|
margin: 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #526172;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-card ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-card li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #2f3b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
BUTTONS
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
background: #125eb0;
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
IMAGE PANEL
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.image-panel img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
TWO COLUMN LAYOUT
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.two-col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.2fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
MODULES
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.module-blocks {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-block {
|
||||||
|
background: #f8fbff;
|
||||||
|
border: 1px solid #dce3ea;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-block h3 {
|
||||||
|
color: #7f9cc0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-result {
|
||||||
|
font-weight: 800;
|
||||||
|
color: #125eb0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-block li {
|
||||||
|
color: #2f3b4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
LOGIN
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text {
|
||||||
|
color: #526172;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form .form-row {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0d2f57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #c9d2db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
background: #ffe7e7;
|
||||||
|
color: #8d1d1d;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
RESPONSIVE
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
|
||||||
|
.pricing-grid,
|
||||||
|
.module-blocks,
|
||||||
|
.two-col {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-nav a {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-inner {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-list {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-list li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
BIN
infra/flask-postgres/._docker-compose.yaml
Normal file
43
infra/flask-postgres/docker-compose.yaml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: ./app
|
||||||
|
container_name: flask_web
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "5050:5000"
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
DB_NAME: CertDB
|
||||||
|
DB_USER: CertUser
|
||||||
|
DB_PASSWORD: CertPWD
|
||||||
|
DB_PORT: 5432
|
||||||
|
LOG_DIR: /logs
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
volumes:
|
||||||
|
- /volume2/container-logs/flask:/logs
|
||||||
|
- /volume1/docker/flask-postgres/app:/app
|
||||||
|
- /volume1/docker/flask-postgres/images:/app/images
|
||||||
|
- /volume1/docker/flask-postgres/styles:/app/styles
|
||||||
|
- /volume1/docker/flask-postgres/files:/app/files
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: flask_db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: CertDB
|
||||||
|
POSTGRES_USER: CertUser
|
||||||
|
POSTGRES_PASSWORD: CertPWD
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker/flask-postgres/db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
image: dpage/pgadmin4
|
||||||
|
container_name: flask_pgadmin
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: admin@kolb.cc
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: DBadmin
|
||||||
|
ports:
|
||||||
|
- "5051:80"
|
||||||
BIN
infra/gitea/._docker-compose.yaml
Normal file
21
infra/gitea/docker-compose.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: gitea/gitea:1.22
|
||||||
|
container_name: gitea-rd
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8088:3000"
|
||||||
|
- "2222:22"
|
||||||
|
environment:
|
||||||
|
USER_UID: 1000
|
||||||
|
USER_GID: 1000
|
||||||
|
|
||||||
|
GITEA__server__DOMAIN: "git.kolb.cc"
|
||||||
|
GITEA__server__ROOT_URL: "https://git.kolb.cc/"
|
||||||
|
GITEA__server__SSH_DOMAIN: "git.kolb.cc"
|
||||||
|
GITEA__server__SSH_PORT: "2222"
|
||||||
|
GITEA__server__PROTOCOL: "http"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker/gitea/data:/data
|
||||||
|
- /volume2/container-logs/gitea:/data/log
|
||||||
BIN
infra/openproject/._docker-compose.yaml
Normal file
29
infra/openproject/docker-compose.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
openproject-db:
|
||||||
|
image: postgres:17
|
||||||
|
container_name: openproject-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: openproject
|
||||||
|
POSTGRES_USER: openproject
|
||||||
|
POSTGRES_PASSWORD: change-me-openproject-db
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker/openproject/pgdata:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
openproject:
|
||||||
|
image: openproject/openproject:17
|
||||||
|
container_name: openproject-rd
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- openproject-db
|
||||||
|
ports:
|
||||||
|
- "8087:80"
|
||||||
|
environment:
|
||||||
|
OPENPROJECT_HOST__NAME: "project.kolb.cc"
|
||||||
|
OPENPROJECT_HTTPS: "true"
|
||||||
|
OPENPROJECT_PROTOCOL: "https"
|
||||||
|
OPENPROJECT_SECRET_KEY_BASE: "change-me-long-random-secret"
|
||||||
|
DATABASE_URL: "postgres://openproject:change-me-openproject-db@openproject-db:5432/openproject"
|
||||||
|
volumes:
|
||||||
|
- /volume1/docker/openproject/assets:/var/openproject/assets
|
||||||
|
- /volume2/container-logs/openproject:/app/log
|
||||||