Initial commit: infra + flask + openproject + gitea

This commit is contained in:
Bernhard Kolb 2026-03-31 15:21:00 +02:00
parent 90159820eb
commit 4647218785
82 changed files with 1313 additions and 152 deletions

BIN
._.gitignore Normal file

Binary file not shown.

181
.gitignore vendored
View File

@ -1,162 +1,39 @@
# ---> Python
# Byte-compiled / optimized / DLL files
# Python
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
# C extensions
*.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 / secrets
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.env.*
!.env.example
# Spyder project settings
.spyderproject
.spyproject
# Logs
*.log
logs/
container-logs/
# Rope project settings
.ropeproject
# macOS
.DS_Store
# mkdocs documentation
/site
# VS Code
.vscode/
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Runtime data
db/
pgdata/
assets/
data/
tmp/
# Pyre type checker
.pyre/
# OpenProject / Gitea persistent data
infra/openproject/pgdata/
infra/openproject/assets/
infra/gitea/data/
# pytype static type analyzer
.pytype/
# 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/
# Flask runtime
app/flask-postgres/files/uploads/
app/flask-postgres/files/runtime/
# Synology
@eaDir/

Binary file not shown.

View 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/

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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"]

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

View File

@ -0,0 +1,4 @@
Flask==3.0.2
gunicorn==22.0.0
psycopg2-binary==2.9.9
Werkzeug==3.0.1

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ 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>

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

View File

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

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

View File

@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ 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>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.