Initial commit: infra + flask + openproject + gitea
BIN
._.gitignore
Normal file
181
.gitignore
vendored
@ -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/
|
||||
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
|
||||